create-stackr 0.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/LICENSE +21 -0
- package/README.md +642 -0
- package/bin/cli.js +12 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/dependencies.d.ts +82 -0
- package/dist/config/dependencies.d.ts.map +1 -0
- package/dist/config/dependencies.js +82 -0
- package/dist/config/dependencies.js.map +1 -0
- package/dist/config/presets.d.ts +3 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +174 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/generators/index.d.ts +40 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +130 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/onboarding.d.ts +8 -0
- package/dist/generators/onboarding.d.ts.map +1 -0
- package/dist/generators/onboarding.js +141 -0
- package/dist/generators/onboarding.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/features.d.ts +14 -0
- package/dist/prompts/features.d.ts.map +1 -0
- package/dist/prompts/features.js +96 -0
- package/dist/prompts/features.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +93 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/onboarding.d.ts +6 -0
- package/dist/prompts/onboarding.d.ts.map +1 -0
- package/dist/prompts/onboarding.js +37 -0
- package/dist/prompts/onboarding.js.map +1 -0
- package/dist/prompts/orm.d.ts +3 -0
- package/dist/prompts/orm.d.ts.map +1 -0
- package/dist/prompts/orm.js +23 -0
- package/dist/prompts/orm.js.map +1 -0
- package/dist/prompts/packageManager.d.ts +2 -0
- package/dist/prompts/packageManager.d.ts.map +1 -0
- package/dist/prompts/packageManager.js +18 -0
- package/dist/prompts/packageManager.js.map +1 -0
- package/dist/prompts/platform.d.ts +3 -0
- package/dist/prompts/platform.d.ts.map +1 -0
- package/dist/prompts/platform.js +21 -0
- package/dist/prompts/platform.js.map +1 -0
- package/dist/prompts/preset.d.ts +4 -0
- package/dist/prompts/preset.d.ts.map +1 -0
- package/dist/prompts/preset.js +165 -0
- package/dist/prompts/preset.js.map +1 -0
- package/dist/prompts/project.d.ts +2 -0
- package/dist/prompts/project.d.ts.map +1 -0
- package/dist/prompts/project.js +27 -0
- package/dist/prompts/project.js.map +1 -0
- package/dist/prompts/sdks.d.ts +2 -0
- package/dist/prompts/sdks.d.ts.map +1 -0
- package/dist/prompts/sdks.js +46 -0
- package/dist/prompts/sdks.js.map +1 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +25 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cleanup.d.ts +5 -0
- package/dist/utils/cleanup.d.ts.map +1 -0
- package/dist/utils/cleanup.js +38 -0
- package/dist/utils/cleanup.js.map +1 -0
- package/dist/utils/copy.d.ts +10 -0
- package/dist/utils/copy.d.ts.map +1 -0
- package/dist/utils/copy.js +53 -0
- package/dist/utils/copy.js.map +1 -0
- package/dist/utils/errors.d.ts +33 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +136 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/git.d.ts +5 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +33 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/package.d.ts +16 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +86 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/system-validation.d.ts +9 -0
- package/dist/utils/system-validation.d.ts.map +1 -0
- package/dist/utils/system-validation.js +31 -0
- package/dist/utils/system-validation.js.map +1 -0
- package/dist/utils/template.d.ts +20 -0
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +234 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +94 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +96 -0
- package/templates/base/backend/.dockerignore.ejs +62 -0
- package/templates/base/backend/.env.example.ejs +116 -0
- package/templates/base/backend/Dockerfile.ejs +142 -0
- package/templates/base/backend/controllers/event-queue/index.ts +20 -0
- package/templates/base/backend/controllers/event-queue/workers/user.ts +39 -0
- package/templates/base/backend/controllers/rest-api/index.ts +48 -0
- package/templates/base/backend/controllers/rest-api/plugins/auth.ts +152 -0
- package/templates/base/backend/controllers/rest-api/plugins/config.ts +64 -0
- package/templates/base/backend/controllers/rest-api/plugins/error-handler.ts +118 -0
- package/templates/base/backend/controllers/rest-api/routes/auth.ts.ejs +180 -0
- package/templates/base/backend/controllers/rest-api/routes/device-sessions.ts +197 -0
- package/templates/base/backend/controllers/rest-api/routes/oauth-web.ts.ejs +375 -0
- package/templates/base/backend/controllers/rest-api/server.ts.ejs +87 -0
- package/templates/base/backend/domain/device-session/repository.drizzle.ts +209 -0
- package/templates/base/backend/domain/device-session/repository.prisma.ts +248 -0
- package/templates/base/backend/domain/device-session/schema.ts +72 -0
- package/templates/base/backend/domain/session/repository.drizzle.ts +72 -0
- package/templates/base/backend/domain/session/repository.prisma.ts +72 -0
- package/templates/base/backend/domain/session/schema.ts +29 -0
- package/templates/base/backend/domain/user/repository.drizzle.ts +127 -0
- package/templates/base/backend/domain/user/repository.prisma.ts +115 -0
- package/templates/base/backend/domain/user/schema.ts +14 -0
- package/templates/base/backend/drizzle/schema.drizzle.ts +111 -0
- package/templates/base/backend/drizzle.config.drizzle.ts +13 -0
- package/templates/base/backend/lib/auth.drizzle.ts.ejs +104 -0
- package/templates/base/backend/lib/auth.prisma.ts.ejs +97 -0
- package/templates/base/backend/lib/constants.ts.ejs +29 -0
- package/templates/base/backend/package.json.ejs +50 -0
- package/templates/base/backend/prisma/schema.prisma.ejs +102 -0
- package/templates/base/backend/prisma.config.prisma.ts +12 -0
- package/templates/base/backend/tsconfig.json +39 -0
- package/templates/base/backend/utils/db.drizzle.ts +41 -0
- package/templates/base/backend/utils/db.prisma.ts +51 -0
- package/templates/base/backend/utils/email.ts.ejs +35 -0
- package/templates/base/backend/utils/errors.ts +348 -0
- package/templates/base/backend/utils/redis.ts.ejs +279 -0
- package/templates/base/mobile/.env.example.ejs +35 -0
- package/templates/base/mobile/.gitignore.ejs +167 -0
- package/templates/base/mobile/app/+not-found.tsx +85 -0
- package/templates/base/mobile/app/_layout.tsx.ejs +71 -0
- package/templates/base/mobile/app.json.ejs +88 -0
- package/templates/base/mobile/assets/images/adaptive-icon.png +0 -0
- package/templates/base/mobile/assets/images/favicon.png +0 -0
- package/templates/base/mobile/assets/images/icon.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_1.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_2.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_3.png +0 -0
- package/templates/base/mobile/assets/images/paywall_image.png +0 -0
- package/templates/base/mobile/assets/images/splash.png +0 -0
- package/templates/base/mobile/eas.json.ejs +49 -0
- package/templates/base/mobile/metro.config.js +9 -0
- package/templates/base/mobile/package.json.ejs +53 -0
- package/templates/base/mobile/src/components/ui/Button.tsx +131 -0
- package/templates/base/mobile/src/components/ui/Card.tsx +68 -0
- package/templates/base/mobile/src/components/ui/IconSymbol.tsx +90 -0
- package/templates/base/mobile/src/components/ui/Input.tsx +142 -0
- package/templates/base/mobile/src/components/ui/LoadingSpinner.tsx +98 -0
- package/templates/base/mobile/src/components/ui/OnboardingLayout.tsx +356 -0
- package/templates/base/mobile/src/components/ui/PaywallLayout.tsx +311 -0
- package/templates/base/mobile/src/components/ui/Skeleton.tsx +58 -0
- package/templates/base/mobile/src/components/ui/index.ts +6 -0
- package/templates/base/mobile/src/constants/Theme.ts +163 -0
- package/templates/base/mobile/src/context/ThemeContext.tsx +157 -0
- package/templates/base/mobile/src/lib/auth-client.ts.ejs +51 -0
- package/templates/base/mobile/src/services/api.ts.ejs +71 -0
- package/templates/base/mobile/src/services/errorService.ts +179 -0
- package/templates/base/mobile/src/services/sdkInitializer.ts.ejs +36 -0
- package/templates/base/mobile/src/store/index.ts.ejs +18 -0
- package/templates/base/mobile/src/store/ui.store.ts +100 -0
- package/templates/base/mobile/src/utils/formatters.ts +105 -0
- package/templates/base/mobile/src/utils/logger.ts +73 -0
- package/templates/base/mobile/src/utils/responsive.ts +234 -0
- package/templates/base/mobile/tsconfig.json +32 -0
- package/templates/base/web/.env.example.ejs +26 -0
- package/templates/base/web/components.json +22 -0
- package/templates/base/web/eslint.config.mjs +18 -0
- package/templates/base/web/next.config.ts +7 -0
- package/templates/base/web/package.json.ejs +35 -0
- package/templates/base/web/postcss.config.mjs +7 -0
- package/templates/base/web/public/.gitkeep +0 -0
- package/templates/base/web/public/file.svg +1 -0
- package/templates/base/web/public/globe.svg +1 -0
- package/templates/base/web/public/next.svg +1 -0
- package/templates/base/web/public/vercel.svg +1 -0
- package/templates/base/web/public/window.svg +1 -0
- package/templates/base/web/src/app/favicon.ico +0 -0
- package/templates/base/web/src/app/globals.css +152 -0
- package/templates/base/web/src/app/layout.tsx.ejs +54 -0
- package/templates/base/web/src/app/page.tsx.ejs +92 -0
- package/templates/base/web/src/components/auth/auth-hydrator.tsx.ejs +19 -0
- package/templates/base/web/src/components/auth/protected-route.tsx.ejs +109 -0
- package/templates/base/web/src/components/providers/device-session-setup.tsx.ejs +56 -0
- package/templates/base/web/src/components/providers/theme-provider.tsx +17 -0
- package/templates/base/web/src/components/theme-toggle.tsx +34 -0
- package/templates/base/web/src/components/ui/button.tsx +62 -0
- package/templates/base/web/src/components/ui/card.tsx +92 -0
- package/templates/base/web/src/components/ui/input.tsx +21 -0
- package/templates/base/web/src/components/ui/label.tsx +24 -0
- package/templates/base/web/src/components/ui/skeleton.tsx +13 -0
- package/templates/base/web/src/components/ui/spinner.tsx +20 -0
- package/templates/base/web/src/hooks/use-device-session.ts.ejs +40 -0
- package/templates/base/web/src/hooks/use-session.ts.ejs +56 -0
- package/templates/base/web/src/lib/auth/actions.ts.ejs +334 -0
- package/templates/base/web/src/lib/auth/config.ts.ejs +65 -0
- package/templates/base/web/src/lib/auth/cookies.ts.ejs +74 -0
- package/templates/base/web/src/lib/auth/index.ts.ejs +40 -0
- package/templates/base/web/src/lib/auth/oauth.ts.ejs +72 -0
- package/templates/base/web/src/lib/auth/pkce.ts.ejs +48 -0
- package/templates/base/web/src/lib/auth/sessions.ts.ejs +135 -0
- package/templates/base/web/src/lib/auth/user-agent.ts.ejs +47 -0
- package/templates/base/web/src/lib/device/actions.ts.ejs +148 -0
- package/templates/base/web/src/lib/device/id.ts.ejs +74 -0
- package/templates/base/web/src/lib/utils.ts +6 -0
- package/templates/base/web/src/proxy.ts.ejs +66 -0
- package/templates/base/web/src/store/auth.store.ts.ejs +89 -0
- package/templates/base/web/src/store/deviceSession.store.ts.ejs +141 -0
- package/templates/base/web/tsconfig.json +34 -0
- package/templates/features/mobile/auth/app/(auth)/_layout.tsx +16 -0
- package/templates/features/mobile/auth/app/(auth)/login.tsx +86 -0
- package/templates/features/mobile/auth/app/(auth)/register.tsx +86 -0
- package/templates/features/mobile/auth/components/auth/LoginForm.tsx.ejs +349 -0
- package/templates/features/mobile/auth/components/auth/RegisterForm.tsx.ejs +407 -0
- package/templates/features/mobile/auth/components/auth/index.ts +2 -0
- package/templates/features/mobile/auth/hooks/index.ts.ejs +1 -0
- package/templates/features/mobile/auth/hooks/useAuth.ts.ejs +367 -0
- package/templates/features/mobile/auth/services/deviceSession.ts +370 -0
- package/templates/features/mobile/auth/store/deviceSession.store.ts +326 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/_layout.tsx.ejs +11 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-1.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-2.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-3.tsx.ejs +60 -0
- package/templates/features/mobile/paywall/app/paywall.tsx +550 -0
- package/templates/features/mobile/tabs/app/(tabs)/_layout.tsx +26 -0
- package/templates/features/mobile/tabs/app/(tabs)/index.tsx +565 -0
- package/templates/features/web/.gitkeep +0 -0
- package/templates/features/web/auth/app/(app)/dashboard/dashboard-client.tsx.ejs +166 -0
- package/templates/features/web/auth/app/(app)/dashboard/page.tsx.ejs +24 -0
- package/templates/features/web/auth/app/(app)/layout.tsx.ejs +43 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/page.tsx.ejs +29 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/sessions-client.tsx.ejs +77 -0
- package/templates/features/web/auth/app/(auth)/forgot-password/page.tsx.ejs +127 -0
- package/templates/features/web/auth/app/(auth)/layout.tsx.ejs +32 -0
- package/templates/features/web/auth/app/(auth)/login/page.tsx.ejs +35 -0
- package/templates/features/web/auth/app/(auth)/register/page.tsx.ejs +19 -0
- package/templates/features/web/auth/app/(auth)/reset-password/page.tsx.ejs +40 -0
- package/templates/features/web/auth/app/(auth)/verify-email/page.tsx.ejs +198 -0
- package/templates/features/web/auth/app/auth/callback/route.ts.ejs +152 -0
- package/templates/features/web/auth/components/auth/login-form.tsx.ejs +100 -0
- package/templates/features/web/auth/components/auth/oauth-buttons.tsx.ejs +126 -0
- package/templates/features/web/auth/components/auth/password-reset-form.tsx.ejs +103 -0
- package/templates/features/web/auth/components/auth/register-form.tsx.ejs +139 -0
- package/templates/features/web/auth/components/settings/session-card.tsx.ejs +132 -0
- package/templates/integrations/mobile/adjust/services/adjustService.ts.ejs +163 -0
- package/templates/integrations/mobile/adjust/store/adjust.store.ts +243 -0
- package/templates/integrations/mobile/att/services/attService.ts +84 -0
- package/templates/integrations/mobile/att/services/trackingPermissions.ts +208 -0
- package/templates/integrations/mobile/att/store/att.store.ts +162 -0
- package/templates/integrations/mobile/revenuecat/services/revenuecatService.ts.ejs +174 -0
- package/templates/integrations/mobile/revenuecat/store/revenuecat.store.ts +286 -0
- package/templates/integrations/mobile/scate/services/scateService.ts.ejs +85 -0
- package/templates/integrations/mobile/scate/store/scate.store.ts +125 -0
- package/templates/integrations/web/.gitkeep +0 -0
- package/templates/shared/.env.example.ejs +21 -0
- package/templates/shared/.gitignore.ejs +145 -0
- package/templates/shared/README.md.ejs +134 -0
- package/templates/shared/docker-compose.prod.yml.ejs +120 -0
- package/templates/shared/docker-compose.yml.ejs +129 -0
- package/templates/shared/scripts/docker-dev.sh.ejs +395 -0
- package/templates/shared/scripts/docker-prod.sh.ejs +542 -0
- package/templates/shared/scripts/setup.sh.ejs +979 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { db } from "../../utils/db";
|
|
2
|
+
import { ErrorFactory } from "../../utils/errors";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* User Repository
|
|
6
|
+
*
|
|
7
|
+
* Note: User creation and authentication is handled by BetterAuth.
|
|
8
|
+
* This repository provides helper functions for user lookups and profile updates.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const getUser = async (id: string) => {
|
|
12
|
+
try {
|
|
13
|
+
const user = await db.user.findUnique({
|
|
14
|
+
where: { id },
|
|
15
|
+
select: {
|
|
16
|
+
id: true,
|
|
17
|
+
email: true,
|
|
18
|
+
emailVerified: true,
|
|
19
|
+
name: true,
|
|
20
|
+
image: true,
|
|
21
|
+
createdAt: true,
|
|
22
|
+
updatedAt: true,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return user;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw ErrorFactory.databaseError({
|
|
28
|
+
operation: 'getUser',
|
|
29
|
+
userId: id,
|
|
30
|
+
originalError: error instanceof Error ? error.message : String(error)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getUserByEmail = async (email: string) => {
|
|
36
|
+
try {
|
|
37
|
+
const user = await db.user.findUnique({
|
|
38
|
+
where: { email },
|
|
39
|
+
select: {
|
|
40
|
+
id: true,
|
|
41
|
+
email: true,
|
|
42
|
+
emailVerified: true,
|
|
43
|
+
name: true,
|
|
44
|
+
image: true,
|
|
45
|
+
createdAt: true,
|
|
46
|
+
updatedAt: true,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
return user;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw ErrorFactory.databaseError({
|
|
52
|
+
operation: 'getUserByEmail',
|
|
53
|
+
email,
|
|
54
|
+
originalError: error instanceof Error ? error.message : String(error)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const isUserExistByEmail = async (email: string): Promise<boolean> => {
|
|
60
|
+
try {
|
|
61
|
+
const userCount = await db.user.count({
|
|
62
|
+
where: { email },
|
|
63
|
+
});
|
|
64
|
+
return userCount > 0;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw ErrorFactory.databaseError({
|
|
67
|
+
operation: 'isUserExistByEmail',
|
|
68
|
+
email,
|
|
69
|
+
originalError: error instanceof Error ? error.message : String(error)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const updateUserProfile = async (
|
|
75
|
+
userId: string,
|
|
76
|
+
data: { name?: string }
|
|
77
|
+
) => {
|
|
78
|
+
try {
|
|
79
|
+
const user = await db.user.update({
|
|
80
|
+
where: { id: userId },
|
|
81
|
+
data,
|
|
82
|
+
select: {
|
|
83
|
+
id: true,
|
|
84
|
+
email: true,
|
|
85
|
+
emailVerified: true,
|
|
86
|
+
name: true,
|
|
87
|
+
image: true,
|
|
88
|
+
createdAt: true,
|
|
89
|
+
updatedAt: true,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
return user;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw ErrorFactory.databaseError({
|
|
95
|
+
operation: 'updateUserProfile',
|
|
96
|
+
userId,
|
|
97
|
+
updateData: data,
|
|
98
|
+
originalError: error instanceof Error ? error.message : String(error)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const deleteUser = async (userId: string): Promise<void> => {
|
|
104
|
+
try {
|
|
105
|
+
await db.user.delete({
|
|
106
|
+
where: { id: userId },
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw ErrorFactory.databaseError({
|
|
110
|
+
operation: 'deleteUser',
|
|
111
|
+
userId,
|
|
112
|
+
originalError: error instanceof Error ? error.message : String(error)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Static, Type } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
// User schema matching BetterAuth's user model
|
|
4
|
+
export const UserSchema = Type.Object({
|
|
5
|
+
id: Type.String(),
|
|
6
|
+
email: Type.String(),
|
|
7
|
+
emailVerified: Type.Boolean(),
|
|
8
|
+
name: Type.Union([Type.String(), Type.Null()]),
|
|
9
|
+
image: Type.Union([Type.String(), Type.Null()]),
|
|
10
|
+
createdAt: Type.String({ format: 'date-time' }),
|
|
11
|
+
updatedAt: Type.String({ format: 'date-time' }),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type User = Static<typeof UserSchema>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle ORM Schema for BetterAuth + Application
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: The user, session, account, and verification tables MUST match
|
|
5
|
+
* BetterAuth's expected schema structure. Do not modify column names or types
|
|
6
|
+
* in these tables without verifying compatibility with BetterAuth.
|
|
7
|
+
*
|
|
8
|
+
* @see https://www.better-auth.com/docs/adapters/drizzle
|
|
9
|
+
* @see https://www.better-auth.com/docs/concepts/database
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { pgTable, text, boolean, timestamp, varchar, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
13
|
+
import { relations } from 'drizzle-orm';
|
|
14
|
+
|
|
15
|
+
// ==================== BetterAuth Tables ====================
|
|
16
|
+
// WARNING: These tables are managed by BetterAuth. Modify with caution.
|
|
17
|
+
|
|
18
|
+
// BetterAuth User model
|
|
19
|
+
export const user = pgTable('user', {
|
|
20
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
21
|
+
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
22
|
+
emailVerified: boolean('email_verified').default(false).notNull(),
|
|
23
|
+
name: text('name'),
|
|
24
|
+
image: text('image'),
|
|
25
|
+
createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(),
|
|
26
|
+
updatedAt: timestamp('updated_at', { mode: 'date' }).defaultNow().notNull().$onUpdate(() => new Date()),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// BetterAuth Session model
|
|
30
|
+
export const session = pgTable('session', {
|
|
31
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
32
|
+
userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
33
|
+
token: text('token').notNull().unique(),
|
|
34
|
+
expiresAt: timestamp('expires_at', { mode: 'date' }).notNull(),
|
|
35
|
+
ipAddress: text('ip_address'),
|
|
36
|
+
userAgent: text('user_agent'),
|
|
37
|
+
createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(),
|
|
38
|
+
updatedAt: timestamp('updated_at', { mode: 'date' }).defaultNow().notNull().$onUpdate(() => new Date()),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// BetterAuth Account model (OAuth connections)
|
|
42
|
+
export const account = pgTable('account', {
|
|
43
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
44
|
+
userId: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
45
|
+
accountId: text('account_id').notNull(),
|
|
46
|
+
providerId: text('provider_id').notNull(),
|
|
47
|
+
accessToken: text('access_token'),
|
|
48
|
+
refreshToken: text('refresh_token'),
|
|
49
|
+
accessTokenExpiresAt: timestamp('access_token_expires_at', { mode: 'date' }),
|
|
50
|
+
refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { mode: 'date' }),
|
|
51
|
+
scope: text('scope'),
|
|
52
|
+
idToken: text('id_token'),
|
|
53
|
+
password: text('password'),
|
|
54
|
+
createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(),
|
|
55
|
+
updatedAt: timestamp('updated_at', { mode: 'date' }).defaultNow().notNull().$onUpdate(() => new Date()),
|
|
56
|
+
}, (table) => ({
|
|
57
|
+
providerAccountIdx: uniqueIndex('provider_account_idx').on(table.providerId, table.accountId),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// BetterAuth Verification model
|
|
61
|
+
export const verification = pgTable('verification', {
|
|
62
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
63
|
+
identifier: text('identifier').notNull(),
|
|
64
|
+
value: text('value').notNull(),
|
|
65
|
+
expiresAt: timestamp('expires_at', { mode: 'date' }).notNull(),
|
|
66
|
+
createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(),
|
|
67
|
+
updatedAt: timestamp('updated_at', { mode: 'date' }).defaultNow().notNull().$onUpdate(() => new Date()),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ==================== App-Specific Tables ====================
|
|
71
|
+
|
|
72
|
+
// Device Session model (anonymous sessions before authentication)
|
|
73
|
+
export const deviceSession = pgTable('device_session', {
|
|
74
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
75
|
+
deviceId: text('device_id').notNull(),
|
|
76
|
+
sessionToken: text('session_token').notNull().unique().$defaultFn(() => crypto.randomUUID()),
|
|
77
|
+
createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(),
|
|
78
|
+
lastActiveAt: timestamp('last_active_at', { mode: 'date' }).defaultNow().notNull(),
|
|
79
|
+
migrated: boolean('migrated').default(false).notNull(),
|
|
80
|
+
migratedToUserId: text('migrated_to_user_id').references(() => user.id, { onDelete: 'set null' }),
|
|
81
|
+
preferredCurrency: varchar('preferred_currency', { length: 3 }).default('USD').notNull(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ==================== Relations ====================
|
|
85
|
+
|
|
86
|
+
export const userRelations = relations(user, ({ many }) => ({
|
|
87
|
+
sessions: many(session),
|
|
88
|
+
accounts: many(account),
|
|
89
|
+
deviceSessions: many(deviceSession),
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
export const sessionRelations = relations(session, ({ one }) => ({
|
|
93
|
+
user: one(user, {
|
|
94
|
+
fields: [session.userId],
|
|
95
|
+
references: [user.id],
|
|
96
|
+
}),
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
export const accountRelations = relations(account, ({ one }) => ({
|
|
100
|
+
user: one(user, {
|
|
101
|
+
fields: [account.userId],
|
|
102
|
+
references: [user.id],
|
|
103
|
+
}),
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
export const deviceSessionRelations = relations(deviceSession, ({ one }) => ({
|
|
107
|
+
migratedToUser: one(user, {
|
|
108
|
+
fields: [deviceSession.migratedToUserId],
|
|
109
|
+
references: [user.id],
|
|
110
|
+
}),
|
|
111
|
+
}));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { defineConfig } from 'drizzle-kit';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
out: './drizzle/migrations',
|
|
6
|
+
schema: './drizzle/schema.ts',
|
|
7
|
+
dialect: 'postgresql',
|
|
8
|
+
dbCredentials: {
|
|
9
|
+
url: process.env.DATABASE_URL!,
|
|
10
|
+
},
|
|
11
|
+
verbose: true,
|
|
12
|
+
strict: true,
|
|
13
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
3
|
+
import { expo } from "@better-auth/expo";
|
|
4
|
+
<% if (features.authentication.twoFactor) { %>
|
|
5
|
+
import { twoFactor } from "better-auth/plugins";
|
|
6
|
+
<% } %>
|
|
7
|
+
import { db } from "../utils/db";
|
|
8
|
+
import * as schema from "../drizzle/schema";
|
|
9
|
+
<% if (features.authentication.emailVerification || features.authentication.passwordReset) { %>
|
|
10
|
+
import { sendEmail } from "../utils/email";
|
|
11
|
+
<% } %>
|
|
12
|
+
|
|
13
|
+
export const auth = betterAuth({
|
|
14
|
+
database: drizzleAdapter(db, {
|
|
15
|
+
provider: "pg",
|
|
16
|
+
schema: {
|
|
17
|
+
user: schema.user,
|
|
18
|
+
session: schema.session,
|
|
19
|
+
account: schema.account,
|
|
20
|
+
verification: schema.verification,
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
basePath: "/api/auth", // BetterAuth endpoints will be at /api/auth/*
|
|
24
|
+
emailAndPassword: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
requireEmailVerification: <%= features.authentication.emailVerification %>,
|
|
27
|
+
<% if (features.authentication.passwordReset) { %>
|
|
28
|
+
sendResetPassword: async ({ user, url }) => {
|
|
29
|
+
await sendEmail({
|
|
30
|
+
to: user.email,
|
|
31
|
+
subject: "Reset your password",
|
|
32
|
+
html: `
|
|
33
|
+
<h1>Reset your password</h1>
|
|
34
|
+
<p>Click the link below to reset your password:</p>
|
|
35
|
+
<a href="${url}">Reset Password</a>
|
|
36
|
+
<p>If you didn't request this, you can safely ignore this email.</p>
|
|
37
|
+
`,
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
<% } %>
|
|
41
|
+
},
|
|
42
|
+
<% if (features.authentication.emailVerification) { %>
|
|
43
|
+
emailVerification: {
|
|
44
|
+
sendVerificationEmail: async ({ user, url }) => {
|
|
45
|
+
await sendEmail({
|
|
46
|
+
to: user.email,
|
|
47
|
+
subject: "Verify your email address",
|
|
48
|
+
html: `
|
|
49
|
+
<h1>Verify your email</h1>
|
|
50
|
+
<p>Click the link below to verify your email address:</p>
|
|
51
|
+
<a href="${url}">Verify Email</a>
|
|
52
|
+
`,
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
<% } %>
|
|
57
|
+
<% if (features.authentication.providers.google || features.authentication.providers.apple || features.authentication.providers.github) { %>
|
|
58
|
+
socialProviders: {
|
|
59
|
+
<% if (features.authentication.providers.google) { %>
|
|
60
|
+
google: {
|
|
61
|
+
clientId: process.env.GOOGLE_WEB_CLIENT_ID!,
|
|
62
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
63
|
+
},
|
|
64
|
+
<% } %>
|
|
65
|
+
<% if (features.authentication.providers.apple) { %>
|
|
66
|
+
apple: {
|
|
67
|
+
clientId: process.env.APPLE_SERVICE_ID!,
|
|
68
|
+
clientSecret: process.env.APPLE_CLIENT_SECRET!,
|
|
69
|
+
appBundleIdentifier: process.env.APPLE_BUNDLE_ID!,
|
|
70
|
+
},
|
|
71
|
+
<% } %>
|
|
72
|
+
<% if (features.authentication.providers.github) { %>
|
|
73
|
+
github: {
|
|
74
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
75
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
76
|
+
},
|
|
77
|
+
<% } %>
|
|
78
|
+
},
|
|
79
|
+
<% } %>
|
|
80
|
+
plugins: [
|
|
81
|
+
expo(), // Required for React Native/Expo OAuth deep link handling
|
|
82
|
+
<% if (features.authentication.twoFactor) { %>
|
|
83
|
+
twoFactor(),
|
|
84
|
+
<% } %>
|
|
85
|
+
],
|
|
86
|
+
session: {
|
|
87
|
+
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
|
88
|
+
updateAge: 60 * 60 * 24, // 1 day
|
|
89
|
+
},
|
|
90
|
+
trustedOrigins: [
|
|
91
|
+
// Web origins (includes development and production URLs)
|
|
92
|
+
...(process.env.TRUSTED_ORIGINS?.split(",").map(o => o.trim()) || ["http://localhost:3000"]),
|
|
93
|
+
// Mobile deep link scheme (required for OAuth callbacks)
|
|
94
|
+
"<%= appScheme %>://",
|
|
95
|
+
// Expo development URLs (development only)
|
|
96
|
+
...(process.env.NODE_ENV === "development" ? ["exp://*/*", "exp://192.168.*.*:*/*"] : []),
|
|
97
|
+
<% if (features.authentication.providers.apple) { %>
|
|
98
|
+
// Apple Sign In origin (required for native iOS sign-in)
|
|
99
|
+
"https://appleid.apple.com",
|
|
100
|
+
<% } %>
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export type Session = typeof auth.$Infer.Session;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
3
|
+
import { expo } from "@better-auth/expo";
|
|
4
|
+
<% if (features.authentication.twoFactor) { %>
|
|
5
|
+
import { twoFactor } from "better-auth/plugins";
|
|
6
|
+
<% } %>
|
|
7
|
+
import { db } from "../utils/db";
|
|
8
|
+
<% if (features.authentication.emailVerification || features.authentication.passwordReset) { %>
|
|
9
|
+
import { sendEmail } from "../utils/email";
|
|
10
|
+
<% } %>
|
|
11
|
+
|
|
12
|
+
export const auth = betterAuth({
|
|
13
|
+
database: prismaAdapter(db, {
|
|
14
|
+
provider: "postgresql",
|
|
15
|
+
}),
|
|
16
|
+
basePath: "/api/auth", // BetterAuth endpoints will be at /api/auth/*
|
|
17
|
+
emailAndPassword: {
|
|
18
|
+
enabled: true,
|
|
19
|
+
requireEmailVerification: <%= features.authentication.emailVerification %>,
|
|
20
|
+
<% if (features.authentication.passwordReset) { %>
|
|
21
|
+
sendResetPassword: async ({ user, url }) => {
|
|
22
|
+
await sendEmail({
|
|
23
|
+
to: user.email,
|
|
24
|
+
subject: "Reset your password",
|
|
25
|
+
html: `
|
|
26
|
+
<h1>Reset your password</h1>
|
|
27
|
+
<p>Click the link below to reset your password:</p>
|
|
28
|
+
<a href="${url}">Reset Password</a>
|
|
29
|
+
<p>If you didn't request this, you can safely ignore this email.</p>
|
|
30
|
+
`,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
<% } %>
|
|
34
|
+
},
|
|
35
|
+
<% if (features.authentication.emailVerification) { %>
|
|
36
|
+
emailVerification: {
|
|
37
|
+
sendVerificationEmail: async ({ user, url }) => {
|
|
38
|
+
await sendEmail({
|
|
39
|
+
to: user.email,
|
|
40
|
+
subject: "Verify your email address",
|
|
41
|
+
html: `
|
|
42
|
+
<h1>Verify your email</h1>
|
|
43
|
+
<p>Click the link below to verify your email address:</p>
|
|
44
|
+
<a href="${url}">Verify Email</a>
|
|
45
|
+
`,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
<% } %>
|
|
50
|
+
<% if (features.authentication.providers.google || features.authentication.providers.apple || features.authentication.providers.github) { %>
|
|
51
|
+
socialProviders: {
|
|
52
|
+
<% if (features.authentication.providers.google) { %>
|
|
53
|
+
google: {
|
|
54
|
+
clientId: process.env.GOOGLE_WEB_CLIENT_ID!,
|
|
55
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
56
|
+
},
|
|
57
|
+
<% } %>
|
|
58
|
+
<% if (features.authentication.providers.apple) { %>
|
|
59
|
+
apple: {
|
|
60
|
+
clientId: process.env.APPLE_SERVICE_ID!,
|
|
61
|
+
clientSecret: process.env.APPLE_CLIENT_SECRET!,
|
|
62
|
+
appBundleIdentifier: process.env.APPLE_BUNDLE_ID!,
|
|
63
|
+
},
|
|
64
|
+
<% } %>
|
|
65
|
+
<% if (features.authentication.providers.github) { %>
|
|
66
|
+
github: {
|
|
67
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
68
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
69
|
+
},
|
|
70
|
+
<% } %>
|
|
71
|
+
},
|
|
72
|
+
<% } %>
|
|
73
|
+
plugins: [
|
|
74
|
+
expo(), // Required for React Native/Expo OAuth deep link handling
|
|
75
|
+
<% if (features.authentication.twoFactor) { %>
|
|
76
|
+
twoFactor(),
|
|
77
|
+
<% } %>
|
|
78
|
+
],
|
|
79
|
+
session: {
|
|
80
|
+
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
|
81
|
+
updateAge: 60 * 60 * 24, // 1 day
|
|
82
|
+
},
|
|
83
|
+
trustedOrigins: [
|
|
84
|
+
// Web origins (includes development and production URLs)
|
|
85
|
+
...(process.env.TRUSTED_ORIGINS?.split(",").map(o => o.trim()) || ["http://localhost:3000"]),
|
|
86
|
+
// Mobile deep link scheme (required for OAuth callbacks)
|
|
87
|
+
"<%= appScheme %>://",
|
|
88
|
+
// Expo development URLs (development only)
|
|
89
|
+
...(process.env.NODE_ENV === "development" ? ["exp://*/*", "exp://192.168.*.*:*/*"] : []),
|
|
90
|
+
<% if (features.authentication.providers.apple) { %>
|
|
91
|
+
// Apple Sign In origin (required for native iOS sign-in)
|
|
92
|
+
"https://appleid.apple.com",
|
|
93
|
+
<% } %>
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export type Session = typeof auth.$Infer.Session;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for authentication
|
|
3
|
+
* Centralized to avoid magic strings scattered across the codebase
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Better Auth cookie name pattern
|
|
7
|
+
export const BETTER_AUTH_COOKIE_NAME = 'better-auth.session_token';
|
|
8
|
+
|
|
9
|
+
// Web-specific cookie names
|
|
10
|
+
export const SESSION_COOKIE_NAME = 'session_token';
|
|
11
|
+
export const DEVICE_SESSION_COOKIE_NAME = 'device_session_token';
|
|
12
|
+
|
|
13
|
+
// OAuth-related cookie names (used in PKCE flow)
|
|
14
|
+
export const OAUTH_PKCE_VERIFIER_COOKIE = 'oauth_pkce_verifier';
|
|
15
|
+
export const OAUTH_STATE_COOKIE = 'oauth_state';
|
|
16
|
+
|
|
17
|
+
// Redis key prefixes
|
|
18
|
+
export const REDIS_KEYS = {
|
|
19
|
+
OAUTH_WEB_STATE: 'oauth:web:', // oauth:web:{state}
|
|
20
|
+
OAUTH_EXCHANGE: 'oauth:exchange:', // oauth:exchange:{token}
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
// TTLs in seconds
|
|
24
|
+
export const TTL = {
|
|
25
|
+
OAUTH_STATE: 600, // 10 minutes
|
|
26
|
+
EXCHANGE_TOKEN: 60, // 1 minute (very short!)
|
|
27
|
+
SESSION: 60 * 60 * 24 * 7, // 7 days
|
|
28
|
+
DEVICE_SESSION: 60 * 60 * 24 * 30, // 30 days
|
|
29
|
+
} as const;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= projectName %>-backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Backend API for <%= projectName %>",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "controllers/rest-api/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev:rest-api": "bun --watch ./controllers/rest-api/index.ts | pino-pretty --colorize",<% if (backend.eventQueue) { %>
|
|
9
|
+
"dev:event-queue": "bun --watch ./controllers/event-queue/index.ts | pino-pretty --colorize",<% } %>
|
|
10
|
+
"build": "bun run tsc",
|
|
11
|
+
"start:rest-api": "bun run dist/controllers/rest-api/index.js",<% if (backend.eventQueue) { %>
|
|
12
|
+
"start:event-queue": "bun run dist/controllers/event-queue/index.js",<% } %><% if (backend.orm === 'prisma') { %>
|
|
13
|
+
"db:generate": "prisma generate",
|
|
14
|
+
"db:push": "prisma db push",
|
|
15
|
+
"db:migrate": "prisma migrate dev",
|
|
16
|
+
"db:studio": "prisma studio",
|
|
17
|
+
"postinstall": "prisma generate"<% } %><% if (backend.orm === 'drizzle') { %>
|
|
18
|
+
"db:generate": "drizzle-kit generate",
|
|
19
|
+
"db:push": "drizzle-kit push",
|
|
20
|
+
"db:migrate": "drizzle-kit migrate",
|
|
21
|
+
"db:studio": "drizzle-kit studio"<% } %>
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@fastify/cors": "^11.0.1",<% if (backend.orm === 'prisma') { %>
|
|
25
|
+
"@prisma/adapter-pg": "^7.0.0",
|
|
26
|
+
"@prisma/client": "^7.0.0",<% } %><% if (backend.orm === 'drizzle') { %>
|
|
27
|
+
"drizzle-orm": "^0.44.0",
|
|
28
|
+
"pg": "^8.13.0",<% } %>
|
|
29
|
+
"@sinclair/typebox": "^0.34.33",
|
|
30
|
+
"ajv": "^8.17.1",
|
|
31
|
+
"better-auth": "^1.4.5",
|
|
32
|
+
"@better-auth/expo": "^1.4.5",
|
|
33
|
+
"dotenv": "^16.5.0",
|
|
34
|
+
"fastify": "^5.3.3",
|
|
35
|
+
"fastify-plugin": "^5.0.1",
|
|
36
|
+
"ioredis": "^5.4.1",
|
|
37
|
+
"pino-pretty": "^13.0.0"<% if (backend.eventQueue) { %>,
|
|
38
|
+
"bullmq": "^5.40.3"<% } %><% if (features.authentication.emailVerification || features.authentication.passwordReset) { %>,
|
|
39
|
+
"nodemailer": "^6.9.0"<% } %>
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^24.0.0",<% if (backend.orm === 'prisma') { %>
|
|
43
|
+
"prisma": "^7.0.0",<% } %><% if (backend.orm === 'drizzle') { %>
|
|
44
|
+
"drizzle-kit": "^0.30.0",
|
|
45
|
+
"@types/pg": "^8.11.0",<% } %>
|
|
46
|
+
"tsx": "^4.20.1",
|
|
47
|
+
"typescript": "^5.8.3"<% if (features.authentication.emailVerification || features.authentication.passwordReset) { %>,
|
|
48
|
+
"@types/nodemailer": "^6.4.0"<% } %>
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client"
|
|
6
|
+
output = "./generated/prisma"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
datasource db {
|
|
10
|
+
provider = "postgresql"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// BetterAuth User model
|
|
14
|
+
// Uses BetterAuth default table name: "user"
|
|
15
|
+
model User {
|
|
16
|
+
id String @id @default(cuid())
|
|
17
|
+
email String @unique
|
|
18
|
+
emailVerified Boolean @default(false)
|
|
19
|
+
name String?
|
|
20
|
+
image String?
|
|
21
|
+
createdAt DateTime @default(now())
|
|
22
|
+
updatedAt DateTime @updatedAt
|
|
23
|
+
|
|
24
|
+
// BetterAuth relations
|
|
25
|
+
sessions Session[]
|
|
26
|
+
accounts Account[]
|
|
27
|
+
|
|
28
|
+
// Device session migration relation
|
|
29
|
+
deviceSessions DeviceSession[]
|
|
30
|
+
|
|
31
|
+
@@map("user")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// BetterAuth Session model (authenticated sessions)
|
|
35
|
+
// Uses BetterAuth default table name: "session"
|
|
36
|
+
model Session {
|
|
37
|
+
id String @id @default(cuid())
|
|
38
|
+
userId String
|
|
39
|
+
token String @unique
|
|
40
|
+
expiresAt DateTime
|
|
41
|
+
ipAddress String?
|
|
42
|
+
userAgent String?
|
|
43
|
+
createdAt DateTime @default(now())
|
|
44
|
+
updatedAt DateTime @updatedAt
|
|
45
|
+
|
|
46
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
47
|
+
|
|
48
|
+
@@map("session")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// BetterAuth Account model (OAuth connections)
|
|
52
|
+
// Uses BetterAuth default table name: "account"
|
|
53
|
+
model Account {
|
|
54
|
+
id String @id @default(cuid())
|
|
55
|
+
userId String
|
|
56
|
+
accountId String
|
|
57
|
+
providerId String
|
|
58
|
+
accessToken String?
|
|
59
|
+
refreshToken String?
|
|
60
|
+
accessTokenExpiresAt DateTime?
|
|
61
|
+
refreshTokenExpiresAt DateTime?
|
|
62
|
+
scope String?
|
|
63
|
+
idToken String?
|
|
64
|
+
password String? // For email/password auth
|
|
65
|
+
createdAt DateTime @default(now())
|
|
66
|
+
updatedAt DateTime @updatedAt
|
|
67
|
+
|
|
68
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
69
|
+
|
|
70
|
+
@@unique([providerId, accountId])
|
|
71
|
+
@@map("account")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// BetterAuth Verification model (email verification, password reset)
|
|
75
|
+
// Uses BetterAuth default table name: "verification"
|
|
76
|
+
model Verification {
|
|
77
|
+
id String @id @default(cuid())
|
|
78
|
+
identifier String
|
|
79
|
+
value String
|
|
80
|
+
expiresAt DateTime
|
|
81
|
+
createdAt DateTime @default(now())
|
|
82
|
+
updatedAt DateTime @updatedAt
|
|
83
|
+
|
|
84
|
+
@@map("verification")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Device Session model (anonymous device sessions before authentication)
|
|
88
|
+
// Renamed from Session to avoid conflict with BetterAuth's Session model
|
|
89
|
+
model DeviceSession {
|
|
90
|
+
id String @id @default(cuid())
|
|
91
|
+
deviceId String
|
|
92
|
+
sessionToken String @unique @default(uuid())
|
|
93
|
+
createdAt DateTime @default(now())
|
|
94
|
+
lastActiveAt DateTime @default(now())
|
|
95
|
+
migrated Boolean @default(false)
|
|
96
|
+
migratedToUserId String?
|
|
97
|
+
preferredCurrency String @default("USD")
|
|
98
|
+
|
|
99
|
+
migratedToUser User? @relation(fields: [migratedToUserId], references: [id], onDelete: SetNull)
|
|
100
|
+
|
|
101
|
+
@@map("device_session")
|
|
102
|
+
}
|