Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions backend/apps/cloud/src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import { GetKeywordsDto } from './dto/get-keywords.dto'
import { GetBotStatsDto } from './dto/get-bot-stats.dto'
import { GSC_ALL_TIME_DAYS, GSCService } from '../project/gsc.service'
import { GetProfileIdDto, GetSessionIdDto } from './dto/get-id.dto'
import { IdentifyDto } from './dto/identify.dto'
import { ExperimentService } from '../experiment/experiment.service'
import {
ExperimentStatus,
Expand Down Expand Up @@ -3297,6 +3298,104 @@ export class AnalyticsController {
return { ...result, appliedFilters, timeBucket: timeBucketForAllTime }
}

/**
* Links the visitor's current anonymous profile to an identified profile
* derived from the user ID supplied by the site (e.g. after log in). Events
* previously recorded for the anonymous profile get attributed to the
* identified profile at query time; the tracker stamps all subsequent
* events with the supplied profileId directly.
*
* Optional traits (email, plan, ...) are stored against the identified
* profile and shown on its dashboard page.
*/
@Post('identify')
@Public()
async identify(
@Body() dto: IdentifyDto,
@Headers() headers,
@Ip() reqIP,
): Promise<{ profileId: string } | typeof BOT_RESPONSE> {
const { 'user-agent': userAgent, origin } = headers
const ip = getIPFromHeaders(headers) || reqIP || ''

await checkRateLimit(ip, 'identify', 120, 60)
await checkRateLimit(dto.pid, 'identify', 2000, 60)

const botResult = await this.analyticsService.checkBot(
dto.pid,
userAgent,
headers,
ip,
headers.referer || headers.referrer,
null,
'identify',
)

if (botResult.isBot) {
return BOT_RESPONSE
}

const profileId = this.analyticsService.validateUserSuppliedProfileId(
dto.profileId,
)

await this.analyticsService.validate(dto, origin, ip)
Comment thread
Blaumaus marked this conversation as resolved.

const userProfileId = await this.analyticsService.generateProfileId(
dto.pid,
userAgent,
ip,
profileId,
)

if (!_isEmpty(dto.traits)) {
await this.analyticsService.saveProfileTraits(
dto.pid,
userProfileId,
dto.traits,
)
}

const anonProfileId = await this.analyticsService.generateProfileId(
dto.pid,
userAgent,
ip,
)

const linked = await this.analyticsService.linkProfiles(
dto.pid,
anonProfileId,
userProfileId,
)

// Flip the visitor's current session (if any) to the identified profile.
// Skipped when the anonymous profile is already linked to a different
// identified profile (e.g. a second account on a shared device) - the
// session stays with the current identity until new events re-stamp it.
if (linked) {
const { exists, psid } = await this.analyticsService.getSessionId(
dto.pid,
userAgent,
ip,
)

if (exists) {
await this.analyticsService.recordSessionActivity(
psid,
dto.pid,
userProfileId,
)
}
}

this.logger.log(
`pid: ${dto.pid}, profileId: ${userProfileId}`,
'POST /analytics/identify',
)

return { profileId: userProfileId }
}

// Revenue attribution endpoints

@Post('profile-id')
Expand Down
Loading
Loading