@windrun-huaiin/backend-core 29.0.3 → 31.0.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/README.md +95 -0
- package/dist/app/api/user/anonymous/init/route.d.ts +1 -1
- package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -1
- package/dist/app/api/user/anonymous/init/route.js +18 -19
- package/dist/app/api/user/anonymous/init/route.mjs +18 -19
- package/dist/app/api/webhook/clerk/user/route.js +16 -16
- package/dist/app/api/webhook/clerk/user/route.mjs +16 -16
- package/dist/auth/auth-utils.d.ts +8 -23
- package/dist/auth/auth-utils.d.ts.map +1 -1
- package/dist/auth/auth-utils.js +8 -20
- package/dist/auth/auth-utils.mjs +8 -20
- package/dist/lib/money-price-config.d.ts +28 -28
- package/dist/lib/money-price-config.js +31 -31
- package/dist/lib/money-price-config.mjs +31 -31
- package/dist/lib/stripe-config.js +3 -3
- package/dist/lib/stripe-config.mjs +3 -3
- package/dist/prisma/prisma-transaction-util.js +1 -1
- package/dist/prisma/prisma-transaction-util.mjs +1 -1
- package/dist/prisma/prisma.d.ts.map +1 -1
- package/dist/prisma/prisma.js +18 -19
- package/dist/prisma/prisma.mjs +18 -19
- package/dist/services/aggregate/billing.aggregate.service.js +6 -6
- package/dist/services/aggregate/billing.aggregate.service.mjs +6 -6
- package/dist/services/aggregate/user.aggregate.service.d.ts +9 -9
- package/dist/services/aggregate/user.aggregate.service.js +16 -16
- package/dist/services/aggregate/user.aggregate.service.mjs +16 -16
- package/dist/services/database/constants.js +34 -34
- package/dist/services/database/constants.mjs +34 -34
- package/dist/services/database/credit.service.js +2 -2
- package/dist/services/database/credit.service.mjs +2 -2
- package/dist/services/database/transaction.service.js +1 -1
- package/dist/services/database/transaction.service.mjs +1 -1
- package/dist/services/database/user.service.js +2 -2
- package/dist/services/database/user.service.mjs +2 -2
- package/dist/services/stripe/webhook-handler.js +5 -5
- package/dist/services/stripe/webhook-handler.mjs +5 -5
- package/package.json +13 -6
- package/src/app/api/user/anonymous/init/route.ts +21 -22
- package/src/app/api/webhook/clerk/user/route.ts +17 -17
- package/src/auth/auth-utils.ts +8 -23
- package/src/lib/money-price-config.ts +31 -32
- package/src/lib/stripe-config.ts +3 -3
- package/src/prisma/prisma-transaction-util.ts +1 -1
- package/src/prisma/prisma.ts +18 -19
- package/src/services/aggregate/billing.aggregate.service.ts +7 -7
- package/src/services/aggregate/user.aggregate.service.ts +16 -16
- package/src/services/database/constants.ts +34 -34
- package/src/services/database/credit.service.ts +2 -2
- package/src/services/database/transaction.service.ts +1 -1
- package/src/services/database/user.service.ts +2 -2
- package/src/services/stripe/webhook-handler.ts +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# @windrun-huaiin/backend-core
|
|
2
|
+
|
|
3
|
+
Shared backend primitives for Next.js applications, including Prisma access, database services, authentication helpers, Stripe integration, credit and pricing server utilities, Upstash utilities, AI services, and ready-to-wire API route handlers.
|
|
4
|
+
|
|
5
|
+
This package is organized around public subpath exports. Import the narrowest entry point that matches the backend capability you need, especially when separating server-only code from route handlers.
|
|
6
|
+
|
|
7
|
+
## Feature Areas
|
|
8
|
+
|
|
9
|
+
### Core Backend Utilities
|
|
10
|
+
|
|
11
|
+
The root entry provides shared backend exports for application code that needs the common backend surface of the package.
|
|
12
|
+
|
|
13
|
+
| Entry | Purpose |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `@windrun-huaiin/backend-core` | Shared backend primitives and common exports |
|
|
16
|
+
|
|
17
|
+
### Prisma and Database Services
|
|
18
|
+
|
|
19
|
+
The Prisma and database entries provide the data-access layer for applications that share database models, service methods, and transaction-oriented backend logic.
|
|
20
|
+
|
|
21
|
+
| Entry | Purpose |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `@windrun-huaiin/backend-core/prisma` | Prisma client and Prisma-related utilities |
|
|
24
|
+
| `@windrun-huaiin/backend-core/database` | Database service layer |
|
|
25
|
+
| `@windrun-huaiin/backend-core/aggregate` | Aggregate service layer |
|
|
26
|
+
| `@windrun-huaiin/backend-core/context` | Backend context helpers |
|
|
27
|
+
|
|
28
|
+
### Authentication
|
|
29
|
+
|
|
30
|
+
Authentication entries provide shared server utilities, shared auth definitions, and middleware helpers for applications that need consistent auth handling.
|
|
31
|
+
|
|
32
|
+
| Entry | Purpose |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `@windrun-huaiin/backend-core/auth/server` | Server-side authentication helpers |
|
|
35
|
+
| `@windrun-huaiin/backend-core/auth/shared` | Shared authentication utilities and definitions |
|
|
36
|
+
| `@windrun-huaiin/backend-core/auth/middleware` | Authentication middleware helpers |
|
|
37
|
+
|
|
38
|
+
### Stripe, Pricing, and Credits
|
|
39
|
+
|
|
40
|
+
Commerce entries cover Stripe integration, checkout and customer portal route handlers, pricing server helpers, and credit server helpers.
|
|
41
|
+
|
|
42
|
+
| Entry | Purpose |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `@windrun-huaiin/backend-core/stripe` | Stripe service layer |
|
|
45
|
+
| `@windrun-huaiin/backend-core/stripe/server` | Server-side Stripe configuration helpers |
|
|
46
|
+
| `@windrun-huaiin/backend-core/pricing/server` | Server-side pricing helpers |
|
|
47
|
+
| `@windrun-huaiin/backend-core/credit/server` | Server-side credit helpers |
|
|
48
|
+
| `@windrun-huaiin/backend-core/config/money-price` | Money-price configuration |
|
|
49
|
+
| `@windrun-huaiin/backend-core/app/api/stripe/checkout/route` | Stripe checkout API route handler |
|
|
50
|
+
| `@windrun-huaiin/backend-core/app/api/stripe/customer-portal/route` | Stripe customer portal API route handler |
|
|
51
|
+
| `@windrun-huaiin/backend-core/app/api/webhook/stripe/route` | Stripe webhook API route handler |
|
|
52
|
+
|
|
53
|
+
### User and Clerk Routes
|
|
54
|
+
|
|
55
|
+
User route handlers provide reusable API endpoints for anonymous user initialization, credit overview, pricing context, and Clerk user webhooks.
|
|
56
|
+
|
|
57
|
+
| Entry | Purpose |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `@windrun-huaiin/backend-core/app/api/user/anonymous/init/route` | Anonymous user initialization API route handler |
|
|
60
|
+
| `@windrun-huaiin/backend-core/app/api/user/credit-overview/route` | User credit overview API route handler |
|
|
61
|
+
| `@windrun-huaiin/backend-core/app/api/user/pricing-context/route` | User pricing context API route handler |
|
|
62
|
+
| `@windrun-huaiin/backend-core/app/api/webhook/clerk/user/route` | Clerk user webhook API route handler |
|
|
63
|
+
|
|
64
|
+
### Fingerprint and Upstash
|
|
65
|
+
|
|
66
|
+
Fingerprint and Upstash entries expose server-side configuration and infrastructure helpers for identity and storage-related backend workflows.
|
|
67
|
+
|
|
68
|
+
| Entry | Purpose |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `@windrun-huaiin/backend-core/config/fingerprint` | Fingerprint configuration |
|
|
71
|
+
| `@windrun-huaiin/backend-core/upstash/server` | Server-side Upstash helpers |
|
|
72
|
+
|
|
73
|
+
### AI Services
|
|
74
|
+
|
|
75
|
+
AI entries provide shared AI service utilities and a reusable API route handler for AI endpoints.
|
|
76
|
+
|
|
77
|
+
| Entry | Purpose |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `@windrun-huaiin/backend-core/ai` | AI service layer |
|
|
80
|
+
| `@windrun-huaiin/backend-core/app/api/ai/route` | AI API route handler |
|
|
81
|
+
|
|
82
|
+
## Usage Notes
|
|
83
|
+
|
|
84
|
+
- Use server-only entries from server components, route handlers, server actions, or backend modules.
|
|
85
|
+
- Use API route handler entries when an application wants to mount the package-provided route behavior directly in a Next.js route.
|
|
86
|
+
- Keep Prisma, database, Stripe, auth, and Upstash imports out of client-side code.
|
|
87
|
+
- Prefer feature-specific subpath imports so each application depends only on the backend capabilities it actually uses.
|
|
88
|
+
|
|
89
|
+
## Installation
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pnpm add @windrun-huaiin/backend-core
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This package is intended for Next.js backend usage. The consuming application is responsible for its environment variables, database setup, authentication provider setup, Stripe setup, and deployment configuration.
|
|
@@ -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;AA4rBxD;;;GAGG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,kCAE9C"}
|
|
@@ -13,13 +13,13 @@ var userContextFinalizer = require('../../../../../services/context/user-context
|
|
|
13
13
|
BigInt.prototype.toJSON = function () {
|
|
14
14
|
return this.toString();
|
|
15
15
|
};
|
|
16
|
-
// ====================
|
|
17
|
-
/**
|
|
16
|
+
// ==================== Utilities ====================
|
|
17
|
+
/** Create a successful response payload */
|
|
18
18
|
function createSuccessResponse(params) {
|
|
19
19
|
const response = Object.assign({ success: true, xUser: userContextService.mapUserToXUser(params.entities.user), xCredit: params.entities.credit ? userContextService.mapCreditToXCredit(params.entities.credit) : null, xSubscription: userContextService.mapSubscriptionToXSubscription(params.entities.subscription), isNewUser: params.isNewUser }, params.options);
|
|
20
20
|
return userContextFinalizer.finalizeUserContext(response);
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/** Create an error response */
|
|
23
23
|
function createErrorResponse(message, status = 400) {
|
|
24
24
|
const errorResponse = { error: message };
|
|
25
25
|
return server$1.NextResponse.json(errorResponse, { status });
|
|
@@ -414,7 +414,7 @@ function finalizeAttribution(sourceRef) {
|
|
|
414
414
|
sourceRef.sourceChannel = 'direct';
|
|
415
415
|
sourceRef.sourceType = 'direct';
|
|
416
416
|
}
|
|
417
|
-
//
|
|
417
|
+
// Extract the user's first-touch attribution source.
|
|
418
418
|
function extractSourceRef(request) {
|
|
419
419
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
420
420
|
const headerRef = request.headers.get('referer') || request.headers.get('referrer');
|
|
@@ -455,7 +455,7 @@ function extractSourceRef(request) {
|
|
|
455
455
|
return Object.keys(sourceRef).length > 0 ? sourceRef : null;
|
|
456
456
|
}
|
|
457
457
|
/**
|
|
458
|
-
*
|
|
458
|
+
* Query the user by Clerk user ID and return response data.
|
|
459
459
|
*/
|
|
460
460
|
function getUserByClerkId(clerkUserId) {
|
|
461
461
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -470,7 +470,7 @@ function getUserByClerkId(clerkUserId) {
|
|
|
470
470
|
});
|
|
471
471
|
}
|
|
472
472
|
/**
|
|
473
|
-
*
|
|
473
|
+
* Query the user by fingerprint ID and return response data.
|
|
474
474
|
*/
|
|
475
475
|
function getUserByFingerprintId(fingerprintId) {
|
|
476
476
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -490,14 +490,14 @@ function getUserByFingerprintId(fingerprintId) {
|
|
|
490
490
|
});
|
|
491
491
|
}
|
|
492
492
|
/**
|
|
493
|
-
*
|
|
493
|
+
* Shared fingerprint request handling logic.
|
|
494
494
|
*/
|
|
495
495
|
function handleFingerprintRequest(request_1) {
|
|
496
496
|
return tslib.__awaiter(this, arguments, void 0, function* (request, options = {}) {
|
|
497
497
|
var _a;
|
|
498
|
-
//
|
|
498
|
+
// Extract the fingerprint ID from the request.
|
|
499
499
|
const fingerprintId = server.extractFingerprintFromNextRequest(request);
|
|
500
|
-
//
|
|
500
|
+
// Validate the fingerprint ID.
|
|
501
501
|
if (!fingerprintId) {
|
|
502
502
|
return createErrorResponse('Invalid or missing fingerprint ID');
|
|
503
503
|
}
|
|
@@ -505,27 +505,26 @@ function handleFingerprintRequest(request_1) {
|
|
|
505
505
|
const authIdentity = yield authUtils.getOptionalServerAuthIdentity();
|
|
506
506
|
const clerkUserId = (_a = authIdentity === null || authIdentity === void 0 ? void 0 : authIdentity.providerUserId) !== null && _a !== void 0 ? _a : null;
|
|
507
507
|
try {
|
|
508
|
-
//
|
|
508
|
+
// Prefer Clerk user ID lookup when the user is authenticated.
|
|
509
509
|
let existingUserResult = null;
|
|
510
510
|
if (clerkUserId) {
|
|
511
|
-
//
|
|
511
|
+
// Authenticated users are always resolved by clerkUserId.
|
|
512
512
|
existingUserResult = yield getUserByClerkId(clerkUserId);
|
|
513
513
|
if (existingUserResult && existingUserResult.xUser.fingerprintId !== fingerprintId) {
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
// 就是以当前登录用户去查他自己的数据就行!
|
|
514
|
+
// The authenticated user's fingerprint changed. Clerk still identifies the account as the same user.
|
|
515
|
+
// Trust clerkUserId as the source of truth and keep resolving the user's own data by login identity.
|
|
516
|
+
// A single fingerprint can be associated with multiple accounts, so no mutation is needed here.
|
|
518
517
|
console.warn(`Current login user used diff fp_ids: ${clerkUserId}, db_fp_id=${existingUserResult.xUser.fingerprintId}, req_fp_id=${fingerprintId}`);
|
|
519
518
|
}
|
|
520
519
|
}
|
|
521
520
|
else {
|
|
522
|
-
//
|
|
521
|
+
// For anonymous requests, fall back to fingerprint lookup.
|
|
523
522
|
existingUserResult = yield getUserByFingerprintId(fingerprintId);
|
|
524
523
|
}
|
|
525
524
|
if (existingUserResult) {
|
|
526
525
|
return server$1.NextResponse.json(existingUserResult);
|
|
527
526
|
}
|
|
528
|
-
//
|
|
527
|
+
// If the user does not exist and creation is disabled, return 404.
|
|
529
528
|
if (!options.createIfNotExists) {
|
|
530
529
|
return createErrorResponse('User not found', 404);
|
|
531
530
|
}
|
|
@@ -534,7 +533,7 @@ function handleFingerprintRequest(request_1) {
|
|
|
534
533
|
if (anonymousInitResult.isNewUser) {
|
|
535
534
|
console.log(`Created new anonymous user ${anonymousInitResult.user.userId} with fingerprint ${fingerprintId}`);
|
|
536
535
|
}
|
|
537
|
-
//
|
|
536
|
+
// Return the created or existing context.
|
|
538
537
|
const response = createSuccessResponse({
|
|
539
538
|
entities: {
|
|
540
539
|
user: anonymousInitResult.user,
|
|
@@ -556,7 +555,7 @@ function handleFingerprintRequest(request_1) {
|
|
|
556
555
|
});
|
|
557
556
|
}
|
|
558
557
|
/**
|
|
559
|
-
*
|
|
558
|
+
* Anonymous user initialization API.
|
|
560
559
|
* POST /api/user/anonymous/init
|
|
561
560
|
*/
|
|
562
561
|
function POST(request) {
|
|
@@ -11,13 +11,13 @@ import { finalizeUserContext } from '../../../../../services/context/user-contex
|
|
|
11
11
|
BigInt.prototype.toJSON = function () {
|
|
12
12
|
return this.toString();
|
|
13
13
|
};
|
|
14
|
-
// ====================
|
|
15
|
-
/**
|
|
14
|
+
// ==================== Utilities ====================
|
|
15
|
+
/** Create a successful response payload */
|
|
16
16
|
function createSuccessResponse(params) {
|
|
17
17
|
const response = Object.assign({ success: true, xUser: mapUserToXUser(params.entities.user), xCredit: params.entities.credit ? mapCreditToXCredit(params.entities.credit) : null, xSubscription: mapSubscriptionToXSubscription(params.entities.subscription), isNewUser: params.isNewUser }, params.options);
|
|
18
18
|
return finalizeUserContext(response);
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
20
|
+
/** Create an error response */
|
|
21
21
|
function createErrorResponse(message, status = 400) {
|
|
22
22
|
const errorResponse = { error: message };
|
|
23
23
|
return NextResponse.json(errorResponse, { status });
|
|
@@ -412,7 +412,7 @@ function finalizeAttribution(sourceRef) {
|
|
|
412
412
|
sourceRef.sourceChannel = 'direct';
|
|
413
413
|
sourceRef.sourceType = 'direct';
|
|
414
414
|
}
|
|
415
|
-
//
|
|
415
|
+
// Extract the user's first-touch attribution source.
|
|
416
416
|
function extractSourceRef(request) {
|
|
417
417
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
418
418
|
const headerRef = request.headers.get('referer') || request.headers.get('referrer');
|
|
@@ -453,7 +453,7 @@ function extractSourceRef(request) {
|
|
|
453
453
|
return Object.keys(sourceRef).length > 0 ? sourceRef : null;
|
|
454
454
|
}
|
|
455
455
|
/**
|
|
456
|
-
*
|
|
456
|
+
* Query the user by Clerk user ID and return response data.
|
|
457
457
|
*/
|
|
458
458
|
function getUserByClerkId(clerkUserId) {
|
|
459
459
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -468,7 +468,7 @@ function getUserByClerkId(clerkUserId) {
|
|
|
468
468
|
});
|
|
469
469
|
}
|
|
470
470
|
/**
|
|
471
|
-
*
|
|
471
|
+
* Query the user by fingerprint ID and return response data.
|
|
472
472
|
*/
|
|
473
473
|
function getUserByFingerprintId(fingerprintId) {
|
|
474
474
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -488,14 +488,14 @@ function getUserByFingerprintId(fingerprintId) {
|
|
|
488
488
|
});
|
|
489
489
|
}
|
|
490
490
|
/**
|
|
491
|
-
*
|
|
491
|
+
* Shared fingerprint request handling logic.
|
|
492
492
|
*/
|
|
493
493
|
function handleFingerprintRequest(request_1) {
|
|
494
494
|
return __awaiter(this, arguments, void 0, function* (request, options = {}) {
|
|
495
495
|
var _a;
|
|
496
|
-
//
|
|
496
|
+
// Extract the fingerprint ID from the request.
|
|
497
497
|
const fingerprintId = extractFingerprintFromNextRequest(request);
|
|
498
|
-
//
|
|
498
|
+
// Validate the fingerprint ID.
|
|
499
499
|
if (!fingerprintId) {
|
|
500
500
|
return createErrorResponse('Invalid or missing fingerprint ID');
|
|
501
501
|
}
|
|
@@ -503,27 +503,26 @@ function handleFingerprintRequest(request_1) {
|
|
|
503
503
|
const authIdentity = yield getOptionalServerAuthIdentity();
|
|
504
504
|
const clerkUserId = (_a = authIdentity === null || authIdentity === void 0 ? void 0 : authIdentity.providerUserId) !== null && _a !== void 0 ? _a : null;
|
|
505
505
|
try {
|
|
506
|
-
//
|
|
506
|
+
// Prefer Clerk user ID lookup when the user is authenticated.
|
|
507
507
|
let existingUserResult = null;
|
|
508
508
|
if (clerkUserId) {
|
|
509
|
-
//
|
|
509
|
+
// Authenticated users are always resolved by clerkUserId.
|
|
510
510
|
existingUserResult = yield getUserByClerkId(clerkUserId);
|
|
511
511
|
if (existingUserResult && existingUserResult.xUser.fingerprintId !== fingerprintId) {
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
// 就是以当前登录用户去查他自己的数据就行!
|
|
512
|
+
// The authenticated user's fingerprint changed. Clerk still identifies the account as the same user.
|
|
513
|
+
// Trust clerkUserId as the source of truth and keep resolving the user's own data by login identity.
|
|
514
|
+
// A single fingerprint can be associated with multiple accounts, so no mutation is needed here.
|
|
516
515
|
console.warn(`Current login user used diff fp_ids: ${clerkUserId}, db_fp_id=${existingUserResult.xUser.fingerprintId}, req_fp_id=${fingerprintId}`);
|
|
517
516
|
}
|
|
518
517
|
}
|
|
519
518
|
else {
|
|
520
|
-
//
|
|
519
|
+
// For anonymous requests, fall back to fingerprint lookup.
|
|
521
520
|
existingUserResult = yield getUserByFingerprintId(fingerprintId);
|
|
522
521
|
}
|
|
523
522
|
if (existingUserResult) {
|
|
524
523
|
return NextResponse.json(existingUserResult);
|
|
525
524
|
}
|
|
526
|
-
//
|
|
525
|
+
// If the user does not exist and creation is disabled, return 404.
|
|
527
526
|
if (!options.createIfNotExists) {
|
|
528
527
|
return createErrorResponse('User not found', 404);
|
|
529
528
|
}
|
|
@@ -532,7 +531,7 @@ function handleFingerprintRequest(request_1) {
|
|
|
532
531
|
if (anonymousInitResult.isNewUser) {
|
|
533
532
|
console.log(`Created new anonymous user ${anonymousInitResult.user.userId} with fingerprint ${fingerprintId}`);
|
|
534
533
|
}
|
|
535
|
-
//
|
|
534
|
+
// Return the created or existing context.
|
|
536
535
|
const response = createSuccessResponse({
|
|
537
536
|
entities: {
|
|
538
537
|
user: anonymousInitResult.user,
|
|
@@ -554,7 +553,7 @@ function handleFingerprintRequest(request_1) {
|
|
|
554
553
|
});
|
|
555
554
|
}
|
|
556
555
|
/**
|
|
557
|
-
*
|
|
556
|
+
* Anonymous user initialization API.
|
|
558
557
|
* POST /api/user/anonymous/init
|
|
559
558
|
*/
|
|
560
559
|
function POST(request) {
|
|
@@ -20,10 +20,10 @@ function POST(request) {
|
|
|
20
20
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
21
21
|
var _a, _b, _c;
|
|
22
22
|
try {
|
|
23
|
-
//
|
|
23
|
+
// Read the raw request body.
|
|
24
24
|
const rawBody = yield request.text();
|
|
25
25
|
let event;
|
|
26
|
-
//
|
|
26
|
+
// Skip signature verification in development.
|
|
27
27
|
if (process.env.NODE_ENV === 'development') {
|
|
28
28
|
console.log('Development mode: skipping webhook signature verification');
|
|
29
29
|
try {
|
|
@@ -35,22 +35,22 @@ function POST(request) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
else {
|
|
38
|
-
//
|
|
38
|
+
// Verify the signature in production.
|
|
39
39
|
const headerPayload = yield headers.headers();
|
|
40
40
|
const svix_id = headerPayload.get('svix-id');
|
|
41
41
|
const svix_timestamp = headerPayload.get('svix-timestamp');
|
|
42
42
|
const svix_signature = headerPayload.get('svix-signature');
|
|
43
|
-
//
|
|
43
|
+
// Reject requests missing required headers.
|
|
44
44
|
if (!svix_id || !svix_timestamp || !svix_signature) {
|
|
45
45
|
return server.NextResponse.json({ error: 'Missing webhook headers' }, { status: 400 });
|
|
46
46
|
}
|
|
47
|
-
//
|
|
47
|
+
// Load the webhook signing secret.
|
|
48
48
|
const webhookSecret = process.env.CLERK_WEBHOOK_SECRET;
|
|
49
49
|
if (!webhookSecret) {
|
|
50
50
|
console.error('CLERK_WEBHOOK_SECRET is not configured');
|
|
51
51
|
return server.NextResponse.json({ error: 'Webhook configuration error' }, { status: 500 });
|
|
52
52
|
}
|
|
53
|
-
//
|
|
53
|
+
// Verify the webhook signature.
|
|
54
54
|
try {
|
|
55
55
|
const wh = new svix.Webhook(webhookSecret);
|
|
56
56
|
event = wh.verify(rawBody, {
|
|
@@ -72,7 +72,7 @@ function POST(request) {
|
|
|
72
72
|
}, event);
|
|
73
73
|
let processingResult = { success: true, message: 'Event processed successfully' };
|
|
74
74
|
try {
|
|
75
|
-
//
|
|
75
|
+
// Dispatch by event type.
|
|
76
76
|
const { type } = event;
|
|
77
77
|
switch (type) {
|
|
78
78
|
case 'user.created':
|
|
@@ -108,7 +108,7 @@ function POST(request) {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
111
|
+
* Handle the user.created event.
|
|
112
112
|
*/
|
|
113
113
|
function handleUserCreated(event) {
|
|
114
114
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -127,7 +127,7 @@ function handleUserCreated(event) {
|
|
|
127
127
|
fingerprintId,
|
|
128
128
|
userName
|
|
129
129
|
});
|
|
130
|
-
//
|
|
130
|
+
// Validate required fields.
|
|
131
131
|
if (!fingerprintId) {
|
|
132
132
|
console.error('Missing fingerprintId in webhook data, process flow error');
|
|
133
133
|
return;
|
|
@@ -137,16 +137,16 @@ function handleUserCreated(event) {
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
try {
|
|
140
|
-
//
|
|
140
|
+
// Find all non-deleted users for this device fingerprint.
|
|
141
141
|
const existingUsers = yield user_service.userService.findListByFingerprintId(fingerprintId);
|
|
142
142
|
if (!existingUsers || existingUsers.length === 0) {
|
|
143
143
|
console.error('Invalid fingerprintId in webhook data, process flow error');
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
|
-
//
|
|
146
|
+
// Find an existing user with the same email.
|
|
147
147
|
const sameEmailUser = existingUsers.find(user => user.email === email);
|
|
148
148
|
if (sameEmailUser) {
|
|
149
|
-
//
|
|
149
|
+
// Same account; update clerkUserId if needed.
|
|
150
150
|
if (sameEmailUser.clerkUserId !== clerkUserId) {
|
|
151
151
|
yield user_service.userService.updateUser(sameEmailUser.userId, { clerkUserId, userName: userName, status: constants.UserStatus.REGISTERED });
|
|
152
152
|
console.log(`Updated clerkUserId for user ${sameEmailUser.userId}`);
|
|
@@ -156,15 +156,15 @@ function handleUserCreated(event) {
|
|
|
156
156
|
}
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
|
-
//
|
|
159
|
+
// Find an anonymous user with no email or clerkUserId.
|
|
160
160
|
const anonymousUser = existingUsers.find(user => !user.email && !user.clerkUserId && user.status === constants.UserStatus.ANONYMOUS);
|
|
161
161
|
if (anonymousUser) {
|
|
162
|
-
//
|
|
162
|
+
// Upgrade the anonymous user.
|
|
163
163
|
yield user_aggregate_service.userAggregateService.upgradeToRegistered(anonymousUser.userId, email, clerkUserId, userName);
|
|
164
164
|
console.log(`Successfully upgraded anonymous user ${anonymousUser.userId} to registered user`);
|
|
165
165
|
return;
|
|
166
166
|
}
|
|
167
|
-
//
|
|
167
|
+
// New account on the same device; create a new user.
|
|
168
168
|
yield user_aggregate_service.userAggregateService.createNewRegisteredUser(clerkUserId, email, fingerprintId, userName);
|
|
169
169
|
console.log(`Created new user for device ${fingerprintId} with email ${email}`);
|
|
170
170
|
}
|
|
@@ -175,7 +175,7 @@ function handleUserCreated(event) {
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
|
-
*
|
|
178
|
+
* Handle the user.deleted event.
|
|
179
179
|
*/
|
|
180
180
|
function handleUserDeleted(event) {
|
|
181
181
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -18,10 +18,10 @@ function POST(request) {
|
|
|
18
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19
19
|
var _a, _b, _c;
|
|
20
20
|
try {
|
|
21
|
-
//
|
|
21
|
+
// Read the raw request body.
|
|
22
22
|
const rawBody = yield request.text();
|
|
23
23
|
let event;
|
|
24
|
-
//
|
|
24
|
+
// Skip signature verification in development.
|
|
25
25
|
if (process.env.NODE_ENV === 'development') {
|
|
26
26
|
console.log('Development mode: skipping webhook signature verification');
|
|
27
27
|
try {
|
|
@@ -33,22 +33,22 @@ function POST(request) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
36
|
-
//
|
|
36
|
+
// Verify the signature in production.
|
|
37
37
|
const headerPayload = yield headers();
|
|
38
38
|
const svix_id = headerPayload.get('svix-id');
|
|
39
39
|
const svix_timestamp = headerPayload.get('svix-timestamp');
|
|
40
40
|
const svix_signature = headerPayload.get('svix-signature');
|
|
41
|
-
//
|
|
41
|
+
// Reject requests missing required headers.
|
|
42
42
|
if (!svix_id || !svix_timestamp || !svix_signature) {
|
|
43
43
|
return NextResponse.json({ error: 'Missing webhook headers' }, { status: 400 });
|
|
44
44
|
}
|
|
45
|
-
//
|
|
45
|
+
// Load the webhook signing secret.
|
|
46
46
|
const webhookSecret = process.env.CLERK_WEBHOOK_SECRET;
|
|
47
47
|
if (!webhookSecret) {
|
|
48
48
|
console.error('CLERK_WEBHOOK_SECRET is not configured');
|
|
49
49
|
return NextResponse.json({ error: 'Webhook configuration error' }, { status: 500 });
|
|
50
50
|
}
|
|
51
|
-
//
|
|
51
|
+
// Verify the webhook signature.
|
|
52
52
|
try {
|
|
53
53
|
const wh = new Webhook(webhookSecret);
|
|
54
54
|
event = wh.verify(rawBody, {
|
|
@@ -70,7 +70,7 @@ function POST(request) {
|
|
|
70
70
|
}, event);
|
|
71
71
|
let processingResult = { success: true, message: 'Event processed successfully' };
|
|
72
72
|
try {
|
|
73
|
-
//
|
|
73
|
+
// Dispatch by event type.
|
|
74
74
|
const { type } = event;
|
|
75
75
|
switch (type) {
|
|
76
76
|
case 'user.created':
|
|
@@ -106,7 +106,7 @@ function POST(request) {
|
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
109
|
+
* Handle the user.created event.
|
|
110
110
|
*/
|
|
111
111
|
function handleUserCreated(event) {
|
|
112
112
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -125,7 +125,7 @@ function handleUserCreated(event) {
|
|
|
125
125
|
fingerprintId,
|
|
126
126
|
userName
|
|
127
127
|
});
|
|
128
|
-
//
|
|
128
|
+
// Validate required fields.
|
|
129
129
|
if (!fingerprintId) {
|
|
130
130
|
console.error('Missing fingerprintId in webhook data, process flow error');
|
|
131
131
|
return;
|
|
@@ -135,16 +135,16 @@ function handleUserCreated(event) {
|
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
try {
|
|
138
|
-
//
|
|
138
|
+
// Find all non-deleted users for this device fingerprint.
|
|
139
139
|
const existingUsers = yield userService.findListByFingerprintId(fingerprintId);
|
|
140
140
|
if (!existingUsers || existingUsers.length === 0) {
|
|
141
141
|
console.error('Invalid fingerprintId in webhook data, process flow error');
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
|
-
//
|
|
144
|
+
// Find an existing user with the same email.
|
|
145
145
|
const sameEmailUser = existingUsers.find(user => user.email === email);
|
|
146
146
|
if (sameEmailUser) {
|
|
147
|
-
//
|
|
147
|
+
// Same account; update clerkUserId if needed.
|
|
148
148
|
if (sameEmailUser.clerkUserId !== clerkUserId) {
|
|
149
149
|
yield userService.updateUser(sameEmailUser.userId, { clerkUserId, userName: userName, status: UserStatus.REGISTERED });
|
|
150
150
|
console.log(`Updated clerkUserId for user ${sameEmailUser.userId}`);
|
|
@@ -154,15 +154,15 @@ function handleUserCreated(event) {
|
|
|
154
154
|
}
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
|
-
//
|
|
157
|
+
// Find an anonymous user with no email or clerkUserId.
|
|
158
158
|
const anonymousUser = existingUsers.find(user => !user.email && !user.clerkUserId && user.status === UserStatus.ANONYMOUS);
|
|
159
159
|
if (anonymousUser) {
|
|
160
|
-
//
|
|
160
|
+
// Upgrade the anonymous user.
|
|
161
161
|
yield userAggregateService.upgradeToRegistered(anonymousUser.userId, email, clerkUserId, userName);
|
|
162
162
|
console.log(`Successfully upgraded anonymous user ${anonymousUser.userId} to registered user`);
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
|
-
//
|
|
165
|
+
// New account on the same device; create a new user.
|
|
166
166
|
yield userAggregateService.createNewRegisteredUser(clerkUserId, email, fingerprintId, userName);
|
|
167
167
|
console.log(`Created new user for device ${fingerprintId} with email ${email}`);
|
|
168
168
|
}
|
|
@@ -173,7 +173,7 @@ function handleUserCreated(event) {
|
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
/**
|
|
176
|
-
*
|
|
176
|
+
* Handle the user.deleted event.
|
|
177
177
|
*/
|
|
178
178
|
function handleUserDeleted(event) {
|
|
179
179
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { NextRequest } from 'next/server';
|
|
2
2
|
import { User } from '../services/database/prisma-model-type';
|
|
3
3
|
import { type AuthProvider, type ProviderIdentity } from './auth-shared';
|
|
4
|
-
/**
|
|
5
|
-
* 认证结果类型
|
|
6
|
-
*/
|
|
7
4
|
export interface AuthResult {
|
|
8
5
|
userId: string;
|
|
9
6
|
user: User;
|
|
@@ -11,48 +8,36 @@ export interface AuthResult {
|
|
|
11
8
|
providerUserId: string;
|
|
12
9
|
}
|
|
13
10
|
/**
|
|
14
|
-
*
|
|
11
|
+
* Fetch User's info from header field by Middleware
|
|
15
12
|
*/
|
|
16
13
|
export declare function getAuthenticatedUser(req: NextRequest): Promise<AuthResult>;
|
|
17
14
|
/**
|
|
18
|
-
*
|
|
15
|
+
* Require Auth, success back user's id
|
|
19
16
|
*/
|
|
20
17
|
export declare function requireAuth(req: NextRequest): Promise<string>;
|
|
21
18
|
/**
|
|
22
|
-
*
|
|
19
|
+
* Require Auth, success back user's info
|
|
23
20
|
*/
|
|
24
21
|
export declare function requireAuthWithUser(req: NextRequest): Promise<AuthResult>;
|
|
25
22
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* Only use in server side
|
|
24
|
+
* Server Component / Server Action, just need user's login status
|
|
28
25
|
*/
|
|
29
26
|
export declare function getOptionalServerAuthIdentity(): Promise<ProviderIdentity | null>;
|
|
30
27
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* Only use in server side
|
|
29
|
+
* Server Component / Server Action, need user's login status and user's data, will check db
|
|
33
30
|
*/
|
|
34
31
|
export declare function getOptionalServerAuthUser(): Promise<AuthResult | null>;
|
|
35
32
|
/**
|
|
36
|
-
* API Route
|
|
33
|
+
* API Route Auth Util
|
|
37
34
|
*/
|
|
38
35
|
export declare class ApiAuthUtils {
|
|
39
36
|
private req;
|
|
40
37
|
constructor(req: NextRequest);
|
|
41
|
-
/**
|
|
42
|
-
* 要求用户必须已认证,返回用户ID
|
|
43
|
-
*/
|
|
44
38
|
requireAuth(): Promise<string>;
|
|
45
|
-
/**
|
|
46
|
-
* 要求用户必须已认证,返回完整用户信息
|
|
47
|
-
*/
|
|
48
39
|
requireAuthWithUser(): Promise<AuthResult>;
|
|
49
|
-
/**
|
|
50
|
-
* 获取用户ID(如果已认证)
|
|
51
|
-
*/
|
|
52
40
|
getUserId(): Promise<string | null>;
|
|
53
|
-
/**
|
|
54
|
-
* 获取完整用户信息(如果已认证)
|
|
55
|
-
*/
|
|
56
41
|
getUser(): Promise<AuthResult | null>;
|
|
57
42
|
}
|
|
58
43
|
//# sourceMappingURL=auth-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-utils.d.ts","sourceRoot":"","sources":["../../src/auth/auth-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,IAAI,EAAE,MAAM,wCAAwC,CAAC;AAC9D,OAAO,EAA6B,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEpG
|
|
1
|
+
{"version":3,"file":"auth-utils.d.ts","sourceRoot":"","sources":["../../src/auth/auth-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,IAAI,EAAE,MAAM,wCAAwC,CAAC;AAC9D,OAAO,EAA6B,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEpG,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAuBhF;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAE/E;AAED;;;GAGG;AACH,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAetF;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAsB5E;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,GAAG,CAAc;gBAEb,GAAG,EAAE,WAAW;IAItB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAI9B,mBAAmB,IAAI,OAAO,CAAC,UAAU,CAAC;IAI1C,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASnC,OAAO,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;CAO5C"}
|