@windrun-huaiin/backend-core 14.1.1 → 14.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -1
- package/dist/app/api/user/anonymous/init/route.js +58 -12
- package/dist/app/api/user/anonymous/init/route.mjs +58 -12
- package/dist/index.js +2 -0
- package/dist/index.mjs +1 -0
- package/dist/services/aggregate/anonymous.aggregate.service.d.ts +21 -0
- package/dist/services/aggregate/anonymous.aggregate.service.d.ts.map +1 -0
- package/dist/services/aggregate/anonymous.aggregate.service.js +87 -0
- package/dist/services/aggregate/anonymous.aggregate.service.mjs +85 -0
- package/dist/services/aggregate/index.d.ts +1 -0
- package/dist/services/aggregate/index.d.ts.map +1 -1
- package/dist/services/aggregate/index.js +2 -0
- package/dist/services/aggregate/index.mjs +1 -0
- package/package.json +2 -2
- package/src/app/api/user/anonymous/init/route.ts +69 -10
- package/src/services/aggregate/anonymous.aggregate.service.ts +113 -0
- package/src/services/aggregate/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/user/anonymous/init/route.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/user/anonymous/init/route.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2rBxD;;;GAGG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,kCAE9C"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var tslib_es6 = require('../../../../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js');
|
|
4
|
-
var
|
|
4
|
+
var anonymous_aggregate_service = require('../../../../../services/aggregate/anonymous.aggregate.service.js');
|
|
5
5
|
var server = require('@windrun-huaiin/third-ui/fingerprint/server');
|
|
6
6
|
var server$1 = require('@clerk/nextjs/server');
|
|
7
7
|
var server$2 = require('next/server');
|
|
@@ -185,6 +185,28 @@ function detectChannelFromPlatform(platform) {
|
|
|
185
185
|
return null;
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
+
function detectChannelFromUtmMedium(value) {
|
|
189
|
+
const normalized = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
|
|
190
|
+
if (!normalized) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
if (/^(cpc|ppc|paid|paid_search|display|banner|affiliate|email|newsletter|push|sms)$/.test(normalized)) {
|
|
194
|
+
return 'campaign';
|
|
195
|
+
}
|
|
196
|
+
if (/^(social|social_paid|social-organic|social_organic)$/.test(normalized)) {
|
|
197
|
+
return 'social';
|
|
198
|
+
}
|
|
199
|
+
if (/^(organic|seo|search)$/.test(normalized)) {
|
|
200
|
+
return 'search';
|
|
201
|
+
}
|
|
202
|
+
if (/^(referral|partner)$/.test(normalized)) {
|
|
203
|
+
return 'referral';
|
|
204
|
+
}
|
|
205
|
+
if (/^(ai|llm)$/.test(normalized)) {
|
|
206
|
+
return 'ai';
|
|
207
|
+
}
|
|
208
|
+
return 'campaign';
|
|
209
|
+
}
|
|
188
210
|
function parseUserAgent(request) {
|
|
189
211
|
var _a, _b, _c, _d, _e;
|
|
190
212
|
const userAgentHeader = request.headers.get('user-agent');
|
|
@@ -311,17 +333,30 @@ function parseFirstTouchHeader(request) {
|
|
|
311
333
|
}
|
|
312
334
|
}
|
|
313
335
|
function finalizeAttribution(sourceRef) {
|
|
314
|
-
var _a, _b, _c, _d;
|
|
336
|
+
var _a, _b, _c, _d, _e;
|
|
315
337
|
const landingHost = normalizeHost(sourceRef.landingHost);
|
|
316
338
|
const refererHost = normalizeHost(sourceRef.refererHost);
|
|
317
339
|
const internal = isInternalReferer(landingHost, refererHost);
|
|
340
|
+
const hasCampaignMarker = Boolean(sourceRef.utmSource
|
|
341
|
+
|| sourceRef.utmMedium
|
|
342
|
+
|| sourceRef.utmCampaign
|
|
343
|
+
|| sourceRef.utmTerm
|
|
344
|
+
|| sourceRef.utmContent
|
|
345
|
+
|| sourceRef.utmId
|
|
346
|
+
|| sourceRef.ref
|
|
347
|
+
|| sourceRef.gclid
|
|
348
|
+
|| sourceRef.fbclid
|
|
349
|
+
|| sourceRef.msclkid
|
|
350
|
+
|| sourceRef.ttclid
|
|
351
|
+
|| sourceRef.twclid
|
|
352
|
+
|| sourceRef.liFatId);
|
|
318
353
|
if (internal) {
|
|
319
354
|
sourceRef.isInternalReferer = true;
|
|
320
355
|
}
|
|
321
356
|
const utmPlatform = detectPlatform(sourceRef.utmSource) || detectPlatform(sourceRef.ref);
|
|
322
357
|
if (utmPlatform) {
|
|
323
358
|
sourceRef.sourcePlatform = utmPlatform;
|
|
324
|
-
sourceRef.sourceChannel = (_b = (_a = detectChannelFromPlatform(utmPlatform)) !== null && _a !== void 0 ? _a : sourceRef.
|
|
359
|
+
sourceRef.sourceChannel = (_c = (_b = (_a = detectChannelFromPlatform(utmPlatform)) !== null && _a !== void 0 ? _a : detectChannelFromUtmMedium(sourceRef.utmMedium)) !== null && _b !== void 0 ? _b : sourceRef.sourceChannel) !== null && _c !== void 0 ? _c : 'campaign';
|
|
325
360
|
sourceRef.sourceType = 'campaign';
|
|
326
361
|
return;
|
|
327
362
|
}
|
|
@@ -361,10 +396,16 @@ function finalizeAttribution(sourceRef) {
|
|
|
361
396
|
sourceRef.sourceType = 'campaign';
|
|
362
397
|
return;
|
|
363
398
|
}
|
|
399
|
+
if (hasCampaignMarker) {
|
|
400
|
+
sourceRef.sourcePlatform = 'other';
|
|
401
|
+
sourceRef.sourceChannel = (_d = detectChannelFromUtmMedium(sourceRef.utmMedium)) !== null && _d !== void 0 ? _d : 'campaign';
|
|
402
|
+
sourceRef.sourceType = 'campaign';
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
364
405
|
if (!internal && refererHost) {
|
|
365
406
|
const refererPlatform = detectPlatform(refererHost) || detectPlatform(sourceRef.httpRefer);
|
|
366
|
-
sourceRef.sourcePlatform =
|
|
367
|
-
sourceRef.sourceChannel = (
|
|
407
|
+
sourceRef.sourcePlatform = refererPlatform !== null && refererPlatform !== void 0 ? refererPlatform : 'other';
|
|
408
|
+
sourceRef.sourceChannel = (_e = detectChannelFromPlatform(refererPlatform)) !== null && _e !== void 0 ? _e : 'referral';
|
|
368
409
|
sourceRef.sourceType = 'referer';
|
|
369
410
|
return;
|
|
370
411
|
}
|
|
@@ -486,17 +527,22 @@ function handleFingerprintRequest(request_1) {
|
|
|
486
527
|
return createErrorResponse('User not found', 404);
|
|
487
528
|
}
|
|
488
529
|
const sourceRef = extractSourceRef(request);
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
530
|
+
const anonymousInitResult = yield anonymous_aggregate_service.anonymousAggregateService.getOrCreateByFingerprintId(fingerprintId, { sourceRef: sourceRef !== null && sourceRef !== void 0 ? sourceRef : undefined });
|
|
531
|
+
if (anonymousInitResult.isNewUser) {
|
|
532
|
+
console.log(`Created new anonymous user ${anonymousInitResult.user.userId} with fingerprint ${fingerprintId}`);
|
|
533
|
+
}
|
|
492
534
|
// 返回创建结果
|
|
493
535
|
const response = createSuccessResponse({
|
|
494
536
|
entities: {
|
|
495
|
-
user:
|
|
496
|
-
credit,
|
|
497
|
-
subscription:
|
|
537
|
+
user: anonymousInitResult.user,
|
|
538
|
+
credit: anonymousInitResult.credit,
|
|
539
|
+
subscription: anonymousInitResult.subscription,
|
|
540
|
+
},
|
|
541
|
+
isNewUser: anonymousInitResult.isNewUser,
|
|
542
|
+
options: {
|
|
543
|
+
totalUsersOnDevice: anonymousInitResult.totalUsersOnDevice,
|
|
544
|
+
hasAnonymousUser: anonymousInitResult.hasAnonymousUser,
|
|
498
545
|
},
|
|
499
|
-
isNewUser: true,
|
|
500
546
|
});
|
|
501
547
|
return server$2.NextResponse.json(response);
|
|
502
548
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __awaiter, __rest } from '../../../../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import { anonymousAggregateService } from '../../../../../services/aggregate/anonymous.aggregate.service.mjs';
|
|
3
3
|
import { extractFingerprintFromNextRequest } from '@windrun-huaiin/third-ui/fingerprint/server';
|
|
4
4
|
import { auth } from '@clerk/nextjs/server';
|
|
5
5
|
import { NextResponse } from 'next/server';
|
|
@@ -183,6 +183,28 @@ function detectChannelFromPlatform(platform) {
|
|
|
183
183
|
return null;
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
+
function detectChannelFromUtmMedium(value) {
|
|
187
|
+
const normalized = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
|
|
188
|
+
if (!normalized) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
if (/^(cpc|ppc|paid|paid_search|display|banner|affiliate|email|newsletter|push|sms)$/.test(normalized)) {
|
|
192
|
+
return 'campaign';
|
|
193
|
+
}
|
|
194
|
+
if (/^(social|social_paid|social-organic|social_organic)$/.test(normalized)) {
|
|
195
|
+
return 'social';
|
|
196
|
+
}
|
|
197
|
+
if (/^(organic|seo|search)$/.test(normalized)) {
|
|
198
|
+
return 'search';
|
|
199
|
+
}
|
|
200
|
+
if (/^(referral|partner)$/.test(normalized)) {
|
|
201
|
+
return 'referral';
|
|
202
|
+
}
|
|
203
|
+
if (/^(ai|llm)$/.test(normalized)) {
|
|
204
|
+
return 'ai';
|
|
205
|
+
}
|
|
206
|
+
return 'campaign';
|
|
207
|
+
}
|
|
186
208
|
function parseUserAgent(request) {
|
|
187
209
|
var _a, _b, _c, _d, _e;
|
|
188
210
|
const userAgentHeader = request.headers.get('user-agent');
|
|
@@ -309,17 +331,30 @@ function parseFirstTouchHeader(request) {
|
|
|
309
331
|
}
|
|
310
332
|
}
|
|
311
333
|
function finalizeAttribution(sourceRef) {
|
|
312
|
-
var _a, _b, _c, _d;
|
|
334
|
+
var _a, _b, _c, _d, _e;
|
|
313
335
|
const landingHost = normalizeHost(sourceRef.landingHost);
|
|
314
336
|
const refererHost = normalizeHost(sourceRef.refererHost);
|
|
315
337
|
const internal = isInternalReferer(landingHost, refererHost);
|
|
338
|
+
const hasCampaignMarker = Boolean(sourceRef.utmSource
|
|
339
|
+
|| sourceRef.utmMedium
|
|
340
|
+
|| sourceRef.utmCampaign
|
|
341
|
+
|| sourceRef.utmTerm
|
|
342
|
+
|| sourceRef.utmContent
|
|
343
|
+
|| sourceRef.utmId
|
|
344
|
+
|| sourceRef.ref
|
|
345
|
+
|| sourceRef.gclid
|
|
346
|
+
|| sourceRef.fbclid
|
|
347
|
+
|| sourceRef.msclkid
|
|
348
|
+
|| sourceRef.ttclid
|
|
349
|
+
|| sourceRef.twclid
|
|
350
|
+
|| sourceRef.liFatId);
|
|
316
351
|
if (internal) {
|
|
317
352
|
sourceRef.isInternalReferer = true;
|
|
318
353
|
}
|
|
319
354
|
const utmPlatform = detectPlatform(sourceRef.utmSource) || detectPlatform(sourceRef.ref);
|
|
320
355
|
if (utmPlatform) {
|
|
321
356
|
sourceRef.sourcePlatform = utmPlatform;
|
|
322
|
-
sourceRef.sourceChannel = (_b = (_a = detectChannelFromPlatform(utmPlatform)) !== null && _a !== void 0 ? _a : sourceRef.
|
|
357
|
+
sourceRef.sourceChannel = (_c = (_b = (_a = detectChannelFromPlatform(utmPlatform)) !== null && _a !== void 0 ? _a : detectChannelFromUtmMedium(sourceRef.utmMedium)) !== null && _b !== void 0 ? _b : sourceRef.sourceChannel) !== null && _c !== void 0 ? _c : 'campaign';
|
|
323
358
|
sourceRef.sourceType = 'campaign';
|
|
324
359
|
return;
|
|
325
360
|
}
|
|
@@ -359,10 +394,16 @@ function finalizeAttribution(sourceRef) {
|
|
|
359
394
|
sourceRef.sourceType = 'campaign';
|
|
360
395
|
return;
|
|
361
396
|
}
|
|
397
|
+
if (hasCampaignMarker) {
|
|
398
|
+
sourceRef.sourcePlatform = 'other';
|
|
399
|
+
sourceRef.sourceChannel = (_d = detectChannelFromUtmMedium(sourceRef.utmMedium)) !== null && _d !== void 0 ? _d : 'campaign';
|
|
400
|
+
sourceRef.sourceType = 'campaign';
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
362
403
|
if (!internal && refererHost) {
|
|
363
404
|
const refererPlatform = detectPlatform(refererHost) || detectPlatform(sourceRef.httpRefer);
|
|
364
|
-
sourceRef.sourcePlatform =
|
|
365
|
-
sourceRef.sourceChannel = (
|
|
405
|
+
sourceRef.sourcePlatform = refererPlatform !== null && refererPlatform !== void 0 ? refererPlatform : 'other';
|
|
406
|
+
sourceRef.sourceChannel = (_e = detectChannelFromPlatform(refererPlatform)) !== null && _e !== void 0 ? _e : 'referral';
|
|
366
407
|
sourceRef.sourceType = 'referer';
|
|
367
408
|
return;
|
|
368
409
|
}
|
|
@@ -484,17 +525,22 @@ function handleFingerprintRequest(request_1) {
|
|
|
484
525
|
return createErrorResponse('User not found', 404);
|
|
485
526
|
}
|
|
486
527
|
const sourceRef = extractSourceRef(request);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
528
|
+
const anonymousInitResult = yield anonymousAggregateService.getOrCreateByFingerprintId(fingerprintId, { sourceRef: sourceRef !== null && sourceRef !== void 0 ? sourceRef : undefined });
|
|
529
|
+
if (anonymousInitResult.isNewUser) {
|
|
530
|
+
console.log(`Created new anonymous user ${anonymousInitResult.user.userId} with fingerprint ${fingerprintId}`);
|
|
531
|
+
}
|
|
490
532
|
// 返回创建结果
|
|
491
533
|
const response = createSuccessResponse({
|
|
492
534
|
entities: {
|
|
493
|
-
user:
|
|
494
|
-
credit,
|
|
495
|
-
subscription:
|
|
535
|
+
user: anonymousInitResult.user,
|
|
536
|
+
credit: anonymousInitResult.credit,
|
|
537
|
+
subscription: anonymousInitResult.subscription,
|
|
538
|
+
},
|
|
539
|
+
isNewUser: anonymousInitResult.isNewUser,
|
|
540
|
+
options: {
|
|
541
|
+
totalUsersOnDevice: anonymousInitResult.totalUsersOnDevice,
|
|
542
|
+
hasAnonymousUser: anonymousInitResult.hasAnonymousUser,
|
|
496
543
|
},
|
|
497
|
-
isNewUser: true,
|
|
498
544
|
});
|
|
499
545
|
return NextResponse.json(response);
|
|
500
546
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ var apilog_service = require('./services/database/apilog.service.js');
|
|
|
12
12
|
var constants = require('./services/database/constants.js');
|
|
13
13
|
var user_aggregate_service = require('./services/aggregate/user.aggregate.service.js');
|
|
14
14
|
var billing_aggregate_service = require('./services/aggregate/billing.aggregate.service.js');
|
|
15
|
+
var anonymous_aggregate_service = require('./services/aggregate/anonymous.aggregate.service.js');
|
|
15
16
|
var userContextService = require('./services/context/user-context-service.js');
|
|
16
17
|
var webhookHandler = require('./services/stripe/webhook-handler.js');
|
|
17
18
|
var moneyPriceConfig = require('./lib/money-price-config.js');
|
|
@@ -60,6 +61,7 @@ exports.isValidTransactionType = constants.isValidTransactionType;
|
|
|
60
61
|
exports.isValidUserStatus = constants.isValidUserStatus;
|
|
61
62
|
exports.userAggregateService = user_aggregate_service.userAggregateService;
|
|
62
63
|
exports.billingAggregateService = billing_aggregate_service.billingAggregateService;
|
|
64
|
+
exports.anonymousAggregateService = anonymous_aggregate_service.anonymousAggregateService;
|
|
63
65
|
exports.applyUserMockContext = userContextService.applyUserMockContext;
|
|
64
66
|
exports.buildInitUserContextFromEntities = userContextService.buildInitUserContextFromEntities;
|
|
65
67
|
exports.fetchLatestUserContextByFingerprintId = userContextService.fetchLatestUserContextByFingerprintId;
|
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ export { Apilogger, apilogService } from './services/database/apilog.service.mjs
|
|
|
10
10
|
export { BillingReason, CreditType, OperationType, OrderStatus, PaySupplier, PaymentStatus, SubscriptionStatus, TransactionType, UserStatus, isValidBillingReason, isValidCreditType, isValidOperationType, isValidOrderStatus, isValidPaymentStatus, isValidSubscriptionStatus, isValidTransactionType, isValidUserStatus } from './services/database/constants.mjs';
|
|
11
11
|
export { userAggregateService } from './services/aggregate/user.aggregate.service.mjs';
|
|
12
12
|
export { billingAggregateService } from './services/aggregate/billing.aggregate.service.mjs';
|
|
13
|
+
export { anonymousAggregateService } from './services/aggregate/anonymous.aggregate.service.mjs';
|
|
13
14
|
export { applyUserMockContext, buildInitUserContextFromEntities, fetchLatestUserContextByFingerprintId, fetchUserContextByClerkUserId, mapCreditToXCredit, mapSubscriptionToXSubscription, mapUserToXUser } from './services/context/user-context-service.mjs';
|
|
14
15
|
export { handleStripeEvent } from './services/stripe/webhook-handler.mjs';
|
|
15
16
|
export { getActiveProviderConfig, getCreditsFromPriceId, getPriceConfig, moneyPriceConfig } from './lib/money-price-config.mjs';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Credit, Subscription, User } from '@/db/prisma-model-type';
|
|
2
|
+
import { Prisma } from '@/db/prisma-model-type';
|
|
3
|
+
type AnonymousInitContext = {
|
|
4
|
+
user: User;
|
|
5
|
+
credit: Credit | null;
|
|
6
|
+
subscription: Subscription | null;
|
|
7
|
+
isNewUser: boolean;
|
|
8
|
+
totalUsersOnDevice: number;
|
|
9
|
+
hasAnonymousUser: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare class AnonymousAggregateService {
|
|
12
|
+
private lockFingerprintInit;
|
|
13
|
+
private findLatestUserContextByFingerprintId;
|
|
14
|
+
private createAnonymousUser;
|
|
15
|
+
getOrCreateByFingerprintId(fingerprintId: string, options?: {
|
|
16
|
+
sourceRef?: Prisma.InputJsonValue;
|
|
17
|
+
}): Promise<AnonymousInitContext>;
|
|
18
|
+
}
|
|
19
|
+
export declare const anonymousAggregateService: AnonymousAggregateService;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=anonymous.aggregate.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anonymous.aggregate.service.d.ts","sourceRoot":"","sources":["../../../src/services/aggregate/anonymous.aggregate.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAIzE,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAIhD,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,cAAM,yBAAyB;YACf,mBAAmB;YAYnB,oCAAoC;YAyBpC,mBAAmB;IAsC3B,0BAA0B,CAC9B,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;KAAE,GAC/C,OAAO,CAAC,oBAAoB,CAAC;CAYjC;AAED,eAAO,MAAM,yBAAyB,2BAAkC,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tslib_es6 = require('../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js');
|
|
4
|
+
var user_service = require('../database/user.service.js');
|
|
5
|
+
var subscription_service = require('../database/subscription.service.js');
|
|
6
|
+
var credit_service = require('../database/credit.service.js');
|
|
7
|
+
var client = require('@prisma/client');
|
|
8
|
+
var constants = require('../database/constants.js');
|
|
9
|
+
require('../../prisma/prisma.js');
|
|
10
|
+
var prismaTransactionUtil = require('../../prisma/prisma-transaction-util.js');
|
|
11
|
+
var creditInit = require('../../lib/credit-init.js');
|
|
12
|
+
|
|
13
|
+
const ANONYMOUS_INIT_LOCK_NAMESPACE = 92831;
|
|
14
|
+
class AnonymousAggregateService {
|
|
15
|
+
lockFingerprintInit(tx, fingerprintId) {
|
|
16
|
+
return tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
yield tx.$executeRaw `
|
|
18
|
+
SELECT pg_advisory_xact_lock(
|
|
19
|
+
${client.Prisma.raw(String(ANONYMOUS_INIT_LOCK_NAMESPACE))},
|
|
20
|
+
hashtext(${fingerprintId})
|
|
21
|
+
)
|
|
22
|
+
`;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
findLatestUserContextByFingerprintId(fingerprintId, tx) {
|
|
26
|
+
return tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
const existingUsers = yield user_service.userService.findListByFingerprintId(fingerprintId, tx);
|
|
28
|
+
if (existingUsers.length === 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const latestUser = existingUsers[0];
|
|
32
|
+
const [credit, subscription] = yield Promise.all([
|
|
33
|
+
credit_service.creditService.getCredit(latestUser.userId, tx),
|
|
34
|
+
subscription_service.subscriptionService.getActiveSubscription(latestUser.userId, tx),
|
|
35
|
+
]);
|
|
36
|
+
return {
|
|
37
|
+
user: latestUser,
|
|
38
|
+
credit,
|
|
39
|
+
subscription,
|
|
40
|
+
isNewUser: false,
|
|
41
|
+
totalUsersOnDevice: existingUsers.length,
|
|
42
|
+
hasAnonymousUser: true,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
createAnonymousUser(fingerprintId, tx, options) {
|
|
47
|
+
return tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const newUser = yield user_service.userService.createUser({
|
|
49
|
+
fingerprintId,
|
|
50
|
+
sourceRef: options === null || options === void 0 ? void 0 : options.sourceRef,
|
|
51
|
+
status: constants.UserStatus.ANONYMOUS,
|
|
52
|
+
}, tx);
|
|
53
|
+
const credit = yield credit_service.creditService.initializeCreditWithFree({
|
|
54
|
+
userId: newUser.userId,
|
|
55
|
+
feature: 'anonymous_user_init',
|
|
56
|
+
creditType: constants.CreditType.FREE,
|
|
57
|
+
operationType: constants.OperationType.SYS_GIFT,
|
|
58
|
+
operationReferId: newUser.userId,
|
|
59
|
+
creditsChange: creditInit.freeAmount,
|
|
60
|
+
}, tx);
|
|
61
|
+
yield subscription_service.subscriptionService.initializeSubscription(newUser.userId, tx);
|
|
62
|
+
return {
|
|
63
|
+
user: newUser,
|
|
64
|
+
credit,
|
|
65
|
+
subscription: null,
|
|
66
|
+
isNewUser: true,
|
|
67
|
+
totalUsersOnDevice: 1,
|
|
68
|
+
hasAnonymousUser: true,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
getOrCreateByFingerprintId(fingerprintId, options) {
|
|
73
|
+
return tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
74
|
+
return prismaTransactionUtil.runInTransaction((tx) => tslib_es6.__awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
yield this.lockFingerprintInit(tx, fingerprintId);
|
|
76
|
+
const existingContext = yield this.findLatestUserContextByFingerprintId(fingerprintId, tx);
|
|
77
|
+
if (existingContext) {
|
|
78
|
+
return existingContext;
|
|
79
|
+
}
|
|
80
|
+
return this.createAnonymousUser(fingerprintId, tx, options);
|
|
81
|
+
}), 'anonymous_get_or_create_by_fingerprint_id');
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const anonymousAggregateService = new AnonymousAggregateService();
|
|
86
|
+
|
|
87
|
+
exports.anonymousAggregateService = anonymousAggregateService;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
|
|
2
|
+
import { userService } from '../database/user.service.mjs';
|
|
3
|
+
import { subscriptionService } from '../database/subscription.service.mjs';
|
|
4
|
+
import { creditService } from '../database/credit.service.mjs';
|
|
5
|
+
import { Prisma } from '@prisma/client';
|
|
6
|
+
import { UserStatus, OperationType, CreditType } from '../database/constants.mjs';
|
|
7
|
+
import '../../prisma/prisma.mjs';
|
|
8
|
+
import { runInTransaction } from '../../prisma/prisma-transaction-util.mjs';
|
|
9
|
+
import { freeAmount } from '../../lib/credit-init.mjs';
|
|
10
|
+
|
|
11
|
+
const ANONYMOUS_INIT_LOCK_NAMESPACE = 92831;
|
|
12
|
+
class AnonymousAggregateService {
|
|
13
|
+
lockFingerprintInit(tx, fingerprintId) {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
yield tx.$executeRaw `
|
|
16
|
+
SELECT pg_advisory_xact_lock(
|
|
17
|
+
${Prisma.raw(String(ANONYMOUS_INIT_LOCK_NAMESPACE))},
|
|
18
|
+
hashtext(${fingerprintId})
|
|
19
|
+
)
|
|
20
|
+
`;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
findLatestUserContextByFingerprintId(fingerprintId, tx) {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const existingUsers = yield userService.findListByFingerprintId(fingerprintId, tx);
|
|
26
|
+
if (existingUsers.length === 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const latestUser = existingUsers[0];
|
|
30
|
+
const [credit, subscription] = yield Promise.all([
|
|
31
|
+
creditService.getCredit(latestUser.userId, tx),
|
|
32
|
+
subscriptionService.getActiveSubscription(latestUser.userId, tx),
|
|
33
|
+
]);
|
|
34
|
+
return {
|
|
35
|
+
user: latestUser,
|
|
36
|
+
credit,
|
|
37
|
+
subscription,
|
|
38
|
+
isNewUser: false,
|
|
39
|
+
totalUsersOnDevice: existingUsers.length,
|
|
40
|
+
hasAnonymousUser: true,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
createAnonymousUser(fingerprintId, tx, options) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
const newUser = yield userService.createUser({
|
|
47
|
+
fingerprintId,
|
|
48
|
+
sourceRef: options === null || options === void 0 ? void 0 : options.sourceRef,
|
|
49
|
+
status: UserStatus.ANONYMOUS,
|
|
50
|
+
}, tx);
|
|
51
|
+
const credit = yield creditService.initializeCreditWithFree({
|
|
52
|
+
userId: newUser.userId,
|
|
53
|
+
feature: 'anonymous_user_init',
|
|
54
|
+
creditType: CreditType.FREE,
|
|
55
|
+
operationType: OperationType.SYS_GIFT,
|
|
56
|
+
operationReferId: newUser.userId,
|
|
57
|
+
creditsChange: freeAmount,
|
|
58
|
+
}, tx);
|
|
59
|
+
yield subscriptionService.initializeSubscription(newUser.userId, tx);
|
|
60
|
+
return {
|
|
61
|
+
user: newUser,
|
|
62
|
+
credit,
|
|
63
|
+
subscription: null,
|
|
64
|
+
isNewUser: true,
|
|
65
|
+
totalUsersOnDevice: 1,
|
|
66
|
+
hasAnonymousUser: true,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
getOrCreateByFingerprintId(fingerprintId, options) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
return runInTransaction((tx) => __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
yield this.lockFingerprintInit(tx, fingerprintId);
|
|
74
|
+
const existingContext = yield this.findLatestUserContextByFingerprintId(fingerprintId, tx);
|
|
75
|
+
if (existingContext) {
|
|
76
|
+
return existingContext;
|
|
77
|
+
}
|
|
78
|
+
return this.createAnonymousUser(fingerprintId, tx, options);
|
|
79
|
+
}), 'anonymous_get_or_create_by_fingerprint_id');
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const anonymousAggregateService = new AnonymousAggregateService();
|
|
84
|
+
|
|
85
|
+
export { anonymousAggregateService };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/aggregate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/aggregate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var user_aggregate_service = require('./user.aggregate.service.js');
|
|
4
4
|
var billing_aggregate_service = require('./billing.aggregate.service.js');
|
|
5
|
+
var anonymous_aggregate_service = require('./anonymous.aggregate.service.js');
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
exports.userAggregateService = user_aggregate_service.userAggregateService;
|
|
9
10
|
exports.billingAggregateService = billing_aggregate_service.billingAggregateService;
|
|
11
|
+
exports.anonymousAggregateService = anonymous_aggregate_service.anonymousAggregateService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/backend-core",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.2.0",
|
|
4
4
|
"description": "Shared backend primitives: Prisma schema/client, database services, routing helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"svix": "^1.86.0",
|
|
96
96
|
"zod": "^4.3.6",
|
|
97
97
|
"@windrun-huaiin/lib": "^14.0.0",
|
|
98
|
-
"@windrun-huaiin/third-ui": "^14.0
|
|
98
|
+
"@windrun-huaiin/third-ui": "^14.1.0"
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
|
101
101
|
"@rollup/plugin-alias": "^5.1.1",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
return this.toString();
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { anonymousAggregateService } from '@/aggregate/anonymous.aggregate.service';
|
|
9
9
|
import type { XCredit, XSubscription, XUser } from '@windrun-huaiin/third-ui/fingerprint';
|
|
10
10
|
import { extractFingerprintFromNextRequest } from '@windrun-huaiin/third-ui/fingerprint/server';
|
|
11
11
|
import { auth } from '@clerk/nextjs/server';
|
|
@@ -306,6 +306,35 @@ function detectChannelFromPlatform(platform: string | null | undefined): string
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
function detectChannelFromUtmMedium(value: string | null | undefined): string | null {
|
|
310
|
+
const normalized = value?.trim().toLowerCase();
|
|
311
|
+
if (!normalized) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (/^(cpc|ppc|paid|paid_search|display|banner|affiliate|email|newsletter|push|sms)$/.test(normalized)) {
|
|
316
|
+
return 'campaign';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (/^(social|social_paid|social-organic|social_organic)$/.test(normalized)) {
|
|
320
|
+
return 'social';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (/^(organic|seo|search)$/.test(normalized)) {
|
|
324
|
+
return 'search';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (/^(referral|partner)$/.test(normalized)) {
|
|
328
|
+
return 'referral';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (/^(ai|llm)$/.test(normalized)) {
|
|
332
|
+
return 'ai';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return 'campaign';
|
|
336
|
+
}
|
|
337
|
+
|
|
309
338
|
function parseUserAgent(request: NextRequest): Pick<SourceRefData, 'userAgent' | 'deviceType' | 'os' | 'browser' | 'secChUaMobile' | 'secChUaPlatform'> {
|
|
310
339
|
const userAgentHeader = request.headers.get('user-agent');
|
|
311
340
|
const secChUaMobile = normalizeQueryParam(request.headers.get('sec-ch-ua-mobile')) ?? undefined;
|
|
@@ -430,6 +459,21 @@ function finalizeAttribution(sourceRef: SourceRefData) {
|
|
|
430
459
|
const landingHost = normalizeHost(sourceRef.landingHost);
|
|
431
460
|
const refererHost = normalizeHost(sourceRef.refererHost);
|
|
432
461
|
const internal = isInternalReferer(landingHost, refererHost);
|
|
462
|
+
const hasCampaignMarker = Boolean(
|
|
463
|
+
sourceRef.utmSource
|
|
464
|
+
|| sourceRef.utmMedium
|
|
465
|
+
|| sourceRef.utmCampaign
|
|
466
|
+
|| sourceRef.utmTerm
|
|
467
|
+
|| sourceRef.utmContent
|
|
468
|
+
|| sourceRef.utmId
|
|
469
|
+
|| sourceRef.ref
|
|
470
|
+
|| sourceRef.gclid
|
|
471
|
+
|| sourceRef.fbclid
|
|
472
|
+
|| sourceRef.msclkid
|
|
473
|
+
|| sourceRef.ttclid
|
|
474
|
+
|| sourceRef.twclid
|
|
475
|
+
|| sourceRef.liFatId
|
|
476
|
+
);
|
|
433
477
|
if (internal) {
|
|
434
478
|
sourceRef.isInternalReferer = true;
|
|
435
479
|
}
|
|
@@ -437,7 +481,10 @@ function finalizeAttribution(sourceRef: SourceRefData) {
|
|
|
437
481
|
const utmPlatform = detectPlatform(sourceRef.utmSource) || detectPlatform(sourceRef.ref);
|
|
438
482
|
if (utmPlatform) {
|
|
439
483
|
sourceRef.sourcePlatform = utmPlatform;
|
|
440
|
-
sourceRef.sourceChannel = detectChannelFromPlatform(utmPlatform)
|
|
484
|
+
sourceRef.sourceChannel = detectChannelFromPlatform(utmPlatform)
|
|
485
|
+
?? detectChannelFromUtmMedium(sourceRef.utmMedium)
|
|
486
|
+
?? sourceRef.sourceChannel
|
|
487
|
+
?? 'campaign';
|
|
441
488
|
sourceRef.sourceType = 'campaign';
|
|
442
489
|
return;
|
|
443
490
|
}
|
|
@@ -484,9 +531,16 @@ function finalizeAttribution(sourceRef: SourceRefData) {
|
|
|
484
531
|
return;
|
|
485
532
|
}
|
|
486
533
|
|
|
534
|
+
if (hasCampaignMarker) {
|
|
535
|
+
sourceRef.sourcePlatform = 'other';
|
|
536
|
+
sourceRef.sourceChannel = detectChannelFromUtmMedium(sourceRef.utmMedium) ?? 'campaign';
|
|
537
|
+
sourceRef.sourceType = 'campaign';
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
487
541
|
if (!internal && refererHost) {
|
|
488
542
|
const refererPlatform = detectPlatform(refererHost) || detectPlatform(sourceRef.httpRefer);
|
|
489
|
-
sourceRef.sourcePlatform = refererPlatform ??
|
|
543
|
+
sourceRef.sourcePlatform = refererPlatform ?? 'other';
|
|
490
544
|
sourceRef.sourceChannel = detectChannelFromPlatform(refererPlatform) ?? 'referral';
|
|
491
545
|
sourceRef.sourceType = 'referer';
|
|
492
546
|
return;
|
|
@@ -624,22 +678,27 @@ async function handleFingerprintRequest(request: NextRequest, options: { createI
|
|
|
624
678
|
|
|
625
679
|
const sourceRef = extractSourceRef(request);
|
|
626
680
|
|
|
627
|
-
|
|
628
|
-
const { newUser, credit } = await userAggregateService.initAnonymousUser(
|
|
681
|
+
const anonymousInitResult = await anonymousAggregateService.getOrCreateByFingerprintId(
|
|
629
682
|
fingerprintId,
|
|
630
683
|
{ sourceRef: sourceRef?? undefined}
|
|
631
684
|
);
|
|
632
685
|
|
|
633
|
-
|
|
686
|
+
if (anonymousInitResult.isNewUser) {
|
|
687
|
+
console.log(`Created new anonymous user ${anonymousInitResult.user.userId} with fingerprint ${fingerprintId}`);
|
|
688
|
+
}
|
|
634
689
|
|
|
635
690
|
// 返回创建结果
|
|
636
691
|
const response = createSuccessResponse({
|
|
637
692
|
entities: {
|
|
638
|
-
user:
|
|
639
|
-
credit,
|
|
640
|
-
subscription:
|
|
693
|
+
user: anonymousInitResult.user,
|
|
694
|
+
credit: anonymousInitResult.credit,
|
|
695
|
+
subscription: anonymousInitResult.subscription,
|
|
696
|
+
},
|
|
697
|
+
isNewUser: anonymousInitResult.isNewUser,
|
|
698
|
+
options: {
|
|
699
|
+
totalUsersOnDevice: anonymousInitResult.totalUsersOnDevice,
|
|
700
|
+
hasAnonymousUser: anonymousInitResult.hasAnonymousUser,
|
|
641
701
|
},
|
|
642
|
-
isNewUser: true,
|
|
643
702
|
});
|
|
644
703
|
return NextResponse.json(response);
|
|
645
704
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { creditService, subscriptionService, userService } from '@/db';
|
|
2
|
+
import { UserStatus } from '@/db/constants';
|
|
3
|
+
import type { Credit, Subscription, User } from '@/db/prisma-model-type';
|
|
4
|
+
import { freeAmount } from '@/lib/credit-init';
|
|
5
|
+
import { runInTransaction } from '@/prisma/prisma-transaction-util';
|
|
6
|
+
import { CreditType, OperationType } from '@/db/constants';
|
|
7
|
+
import { Prisma } from '@/db/prisma-model-type';
|
|
8
|
+
|
|
9
|
+
const ANONYMOUS_INIT_LOCK_NAMESPACE = 92831;
|
|
10
|
+
|
|
11
|
+
type AnonymousInitContext = {
|
|
12
|
+
user: User;
|
|
13
|
+
credit: Credit | null;
|
|
14
|
+
subscription: Subscription | null;
|
|
15
|
+
isNewUser: boolean;
|
|
16
|
+
totalUsersOnDevice: number;
|
|
17
|
+
hasAnonymousUser: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class AnonymousAggregateService {
|
|
21
|
+
private async lockFingerprintInit(
|
|
22
|
+
tx: Prisma.TransactionClient,
|
|
23
|
+
fingerprintId: string,
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
await tx.$executeRaw`
|
|
26
|
+
SELECT pg_advisory_xact_lock(
|
|
27
|
+
${Prisma.raw(String(ANONYMOUS_INIT_LOCK_NAMESPACE))},
|
|
28
|
+
hashtext(${fingerprintId})
|
|
29
|
+
)
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async findLatestUserContextByFingerprintId(
|
|
34
|
+
fingerprintId: string,
|
|
35
|
+
tx: Prisma.TransactionClient,
|
|
36
|
+
): Promise<AnonymousInitContext | null> {
|
|
37
|
+
const existingUsers = await userService.findListByFingerprintId(fingerprintId, tx);
|
|
38
|
+
if (existingUsers.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const latestUser = existingUsers[0];
|
|
43
|
+
const [credit, subscription] = await Promise.all([
|
|
44
|
+
creditService.getCredit(latestUser.userId, tx),
|
|
45
|
+
subscriptionService.getActiveSubscription(latestUser.userId, tx),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
user: latestUser,
|
|
50
|
+
credit,
|
|
51
|
+
subscription,
|
|
52
|
+
isNewUser: false,
|
|
53
|
+
totalUsersOnDevice: existingUsers.length,
|
|
54
|
+
hasAnonymousUser: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async createAnonymousUser(
|
|
59
|
+
fingerprintId: string,
|
|
60
|
+
tx: Prisma.TransactionClient,
|
|
61
|
+
options?: { sourceRef?: Prisma.InputJsonValue; },
|
|
62
|
+
): Promise<AnonymousInitContext> {
|
|
63
|
+
const newUser = await userService.createUser(
|
|
64
|
+
{
|
|
65
|
+
fingerprintId,
|
|
66
|
+
sourceRef: options?.sourceRef,
|
|
67
|
+
status: UserStatus.ANONYMOUS,
|
|
68
|
+
},
|
|
69
|
+
tx,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const credit = await creditService.initializeCreditWithFree(
|
|
73
|
+
{
|
|
74
|
+
userId: newUser.userId,
|
|
75
|
+
feature: 'anonymous_user_init',
|
|
76
|
+
creditType: CreditType.FREE,
|
|
77
|
+
operationType: OperationType.SYS_GIFT,
|
|
78
|
+
operationReferId: newUser.userId,
|
|
79
|
+
creditsChange: freeAmount,
|
|
80
|
+
},
|
|
81
|
+
tx,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await subscriptionService.initializeSubscription(newUser.userId, tx);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
user: newUser,
|
|
88
|
+
credit,
|
|
89
|
+
subscription: null,
|
|
90
|
+
isNewUser: true,
|
|
91
|
+
totalUsersOnDevice: 1,
|
|
92
|
+
hasAnonymousUser: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getOrCreateByFingerprintId(
|
|
97
|
+
fingerprintId: string,
|
|
98
|
+
options?: { sourceRef?: Prisma.InputJsonValue; },
|
|
99
|
+
): Promise<AnonymousInitContext> {
|
|
100
|
+
return runInTransaction(async (tx) => {
|
|
101
|
+
await this.lockFingerprintInit(tx, fingerprintId);
|
|
102
|
+
|
|
103
|
+
const existingContext = await this.findLatestUserContextByFingerprintId(fingerprintId, tx);
|
|
104
|
+
if (existingContext) {
|
|
105
|
+
return existingContext;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return this.createAnonymousUser(fingerprintId, tx, options);
|
|
109
|
+
}, 'anonymous_get_or_create_by_fingerprint_id');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const anonymousAggregateService = new AnonymousAggregateService();
|