@thunder-stack/create-thunder-app 0.0.1

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.
Files changed (69) hide show
  1. package/index.js +229 -0
  2. package/package.json +26 -0
  3. package/postpublish.js +14 -0
  4. package/prepublish.js +68 -0
  5. package/template/.env.example +13 -0
  6. package/template/README.md +1 -0
  7. package/template/THUNDER_STACK.md +120 -0
  8. package/template/client/assets/logo.svg +36 -0
  9. package/template/client/expo/App.test.tsx +20 -0
  10. package/template/client/expo/App.tsx +16 -0
  11. package/template/client/expo/app.json +30 -0
  12. package/template/client/expo/babel.config.js +12 -0
  13. package/template/client/expo/global.css +3 -0
  14. package/template/client/expo/jest.config.js +7 -0
  15. package/template/client/expo/metro.config.js +21 -0
  16. package/template/client/expo/package.json +33 -0
  17. package/template/client/expo/tsconfig.json +6 -0
  18. package/template/client/next/app/globals.css +14 -0
  19. package/template/client/next/app/layout.tsx +28 -0
  20. package/template/client/next/app/page.test.tsx +13 -0
  21. package/template/client/next/app/page.tsx +10 -0
  22. package/template/client/next/next-env.d.ts +5 -0
  23. package/template/client/next/next.config.js +21 -0
  24. package/template/client/next/package.json +39 -0
  25. package/template/client/next/playwright.config.ts +17 -0
  26. package/template/client/next/postcss.config.js +6 -0
  27. package/template/client/next/tsconfig.json +30 -0
  28. package/template/client/next/vitest.config.ts +11 -0
  29. package/template/client/next/vitest.setup.ts +1 -0
  30. package/template/client/shared/index.ts +2 -0
  31. package/template/client/shared/package.json +24 -0
  32. package/template/client/shared/src/components/Button.tsx +38 -0
  33. package/template/client/shared/src/components/Card.tsx +17 -0
  34. package/template/client/shared/src/features/auth/login.tsx +139 -0
  35. package/template/client/shared/src/features/dashboard/index.tsx +232 -0
  36. package/template/client/shared/src/features/home/screen.tsx +212 -0
  37. package/template/client/shared/src/features/management/roles.tsx +356 -0
  38. package/template/client/shared/src/features/management/users.tsx +245 -0
  39. package/template/client/shared/src/nativewind-env.d.ts +1 -0
  40. package/template/client/shared/src/utils/auth.ts +21 -0
  41. package/template/client/shared/tailwind.config.js +19 -0
  42. package/template/client/shared/tsconfig.json +9 -0
  43. package/template/package.json +47 -0
  44. package/template/packages/tsconfig/base.json +20 -0
  45. package/template/packages/tsconfig/package.json +11 -0
  46. package/template/pnpm-workspace.yaml +4 -0
  47. package/template/scripts/clean.js +56 -0
  48. package/template/server/db/drizzle.config.ts +10 -0
  49. package/template/server/db/migrations/0000_loving_mindworm.sql +86 -0
  50. package/template/server/db/migrations/meta/0000_snapshot.json +549 -0
  51. package/template/server/db/migrations/meta/_journal.json +13 -0
  52. package/template/server/db/package.json +28 -0
  53. package/template/server/db/src/index.ts +16 -0
  54. package/template/server/db/src/migrate.ts +28 -0
  55. package/template/server/db/src/schema/auth.ts +73 -0
  56. package/template/server/db/src/schema/index.ts +2 -0
  57. package/template/server/db/src/schema/rbac.ts +72 -0
  58. package/template/server/db/src/schema.ts +1 -0
  59. package/template/server/db/src/seed.ts +204 -0
  60. package/template/server/db/src/services/rbac.ts +144 -0
  61. package/template/server/db/src/services/user.ts +34 -0
  62. package/template/server/db/src/types/index.ts +34 -0
  63. package/template/server/db/tsconfig.json +8 -0
  64. package/template/server/hono/package.json +22 -0
  65. package/template/server/hono/src/auth.ts +29 -0
  66. package/template/server/hono/src/index.ts +215 -0
  67. package/template/server/hono/tsconfig.json +8 -0
  68. package/template/server/hono/wrangler.toml +12 -0
  69. package/template/turbo.json +68 -0
@@ -0,0 +1,245 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { View as MotiView } from "moti";
3
+ import { View, Text, ActivityIndicator, ScrollView, RefreshControl, Image, TouchableOpacity } from "react-native";
4
+ import { Card } from "../../components/Card";
5
+ import { authClient } from "../../utils/auth";
6
+
7
+ function UserListSkeleton() {
8
+ return (
9
+ <ScrollView className="flex-1">
10
+ <View className="p-6 gap-6">
11
+ <View>
12
+ <View className="h-3 w-20 bg-slate-800 rounded mb-2" />
13
+ <View className="h-8 w-48 bg-slate-800 rounded" />
14
+ </View>
15
+
16
+ <View className="gap-4">
17
+ {[1, 2, 3].map((i) => (
18
+ <MotiView
19
+ key={i}
20
+ from={{ opacity: 0.3 }}
21
+ animate={{ opacity: 0.7 }}
22
+ transition={{
23
+ type: "timing",
24
+ duration: 900,
25
+ loop: true,
26
+ delay: i * 150,
27
+ }}
28
+ className="bg-slate-900/40 border-slate-800 p-5 rounded-2xl border flex-row items-center justify-between gap-4 min-h-[90px]"
29
+ >
30
+ <View className="flex-row items-center gap-4">
31
+ <View className="w-12 h-12 rounded-full bg-slate-800" />
32
+ <View className="gap-2">
33
+ <View className="h-4 w-32 bg-slate-800 rounded" />
34
+ <View className="h-3 w-48 bg-slate-800 rounded" />
35
+ </View>
36
+ </View>
37
+ <View className="flex-row gap-2">
38
+ <View className="w-24 h-8 bg-slate-800 rounded-lg" />
39
+ <View className="w-24 h-8 bg-slate-800 rounded-lg" />
40
+ </View>
41
+ </MotiView>
42
+ ))}
43
+ </View>
44
+ </View>
45
+ </ScrollView>
46
+ );
47
+ }
48
+
49
+ export function UserManagementView() {
50
+ const [users, setUsers] = useState<any[]>([]);
51
+ const [loading, setLoading] = useState(true);
52
+ const [refreshing, setRefreshing] = useState(false);
53
+ const [togglingUser, setTogglingUser] = useState<string | null>(null);
54
+ const [error, setError] = useState("");
55
+
56
+ const fetchUsers = async () => {
57
+ try {
58
+ const res = await fetch(`${authClient.baseURL}/api/management/users`, {
59
+ headers: {
60
+ "Content-Type": "application/json",
61
+ },
62
+ });
63
+ const data = await res.json();
64
+ if (res.status === 403 || res.status === 401) {
65
+ setError(data.error || "Permission denied.");
66
+ } else if (!data.error) {
67
+ setUsers(data);
68
+ }
69
+ } catch (err) {
70
+ console.error("Failed to load users:", err);
71
+ setError("Failed to fetch user list.");
72
+ } finally {
73
+ setLoading(false);
74
+ setRefreshing(false);
75
+ }
76
+ };
77
+
78
+ useEffect(() => {
79
+ fetchUsers();
80
+ }, []);
81
+
82
+ const handleToggleRole = async (userId: string, currentRoles: string[], targetRoleId: string, targetRoleName: string) => {
83
+ setTogglingUser(userId);
84
+ setError("");
85
+ const hasRole = currentRoles.includes(targetRoleName);
86
+
87
+ try {
88
+ const url = `${authClient.baseURL}/api/management/users/${userId}/roles${hasRole ? `/${targetRoleId}` : ""}`;
89
+ const method = hasRole ? "DELETE" : "POST";
90
+
91
+ const res = await fetch(url, {
92
+ method,
93
+ headers: {
94
+ "Content-Type": "application/json",
95
+ },
96
+ body: method === "POST" ? JSON.stringify({ roleId: targetRoleId }) : undefined,
97
+ });
98
+
99
+ if (!res.ok) {
100
+ const errorData = await res.json();
101
+ throw new Error(errorData.error || "Failed to update role.");
102
+ }
103
+
104
+ await fetchUsers();
105
+ } catch (err: any) {
106
+ console.error("Failed to toggle role:", err);
107
+ setError(err?.message || "An error occurred while modifying roles.");
108
+ } finally {
109
+ setTogglingUser(null);
110
+ }
111
+ };
112
+
113
+ const onRefresh = () => {
114
+ setRefreshing(true);
115
+ fetchUsers();
116
+ };
117
+
118
+ if (loading) {
119
+ return <UserListSkeleton />;
120
+ }
121
+
122
+ return (
123
+ <ScrollView
124
+ className="flex-1"
125
+ refreshControl={
126
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#3b82f6" />
127
+ }
128
+ >
129
+ <View className="p-6 gap-6">
130
+ <MotiView
131
+ from={{ opacity: 0, translateY: -10 }}
132
+ animate={{ opacity: 1, translateY: 0 }}
133
+ transition={{ type: "timing", duration: 400 }}
134
+ >
135
+ <Text className="text-slate-400 text-xs font-bold uppercase tracking-widest mb-1">
136
+ Administration
137
+ </Text>
138
+ <Text className="text-white text-3xl font-black tracking-tight">
139
+ User Management
140
+ </Text>
141
+ </MotiView>
142
+
143
+ {error ? (
144
+ <View className="p-4 bg-red-950/40 border border-red-500/20 rounded-xl">
145
+ <Text className="text-red-400 text-xs font-semibold text-center">{error}</Text>
146
+ </View>
147
+ ) : null}
148
+
149
+ <View className="gap-4">
150
+ {users.map((item, index) => {
151
+ const userRoles = item.roles.map((r: any) => r.name);
152
+ const isAdmin = userRoles.includes("Admin");
153
+ const isUser = userRoles.includes("User");
154
+
155
+ return (
156
+ <MotiView
157
+ key={item.id}
158
+ from={{ opacity: 0, translateY: 15 }}
159
+ animate={{ opacity: 1, translateY: 0 }}
160
+ transition={{ type: "timing", duration: 400, delay: index * 50 }}
161
+ >
162
+ <Card className="bg-slate-900/40 border-slate-800 p-5 rounded-2xl border flex-row flex-wrap items-center justify-between gap-4">
163
+ <View className="flex-row items-center gap-4">
164
+ {item.image ? (
165
+ <Image source={{ uri: item.image }} className="w-12 h-12 rounded-full border border-slate-700" />
166
+ ) : (
167
+ <View className="w-12 h-12 rounded-full bg-slate-800 border border-slate-700 items-center justify-center">
168
+ <Text className="text-white text-lg font-bold">
169
+ {item.name.charAt(0)}
170
+ </Text>
171
+ </View>
172
+ )}
173
+ <View>
174
+ <Text className="text-white font-extrabold text-base">{item.name}</Text>
175
+ <Text className="text-slate-400 text-xs font-medium">{item.email}</Text>
176
+ <View className="flex-row gap-2 mt-2">
177
+ {userRoles.map((roleName: string) => (
178
+ <View
179
+ key={roleName}
180
+ className={`px-2 py-0.5 rounded-md border ${
181
+ roleName === "Admin"
182
+ ? "bg-blue-500/10 border-blue-500/25"
183
+ : "bg-slate-500/10 border-slate-500/20"
184
+ }`}
185
+ >
186
+ <Text
187
+ className={`text-[10px] font-bold ${
188
+ roleName === "Admin" ? "text-blue-400" : "text-slate-400"
189
+ }`}
190
+ >
191
+ {roleName}
192
+ </Text>
193
+ </View>
194
+ ))}
195
+ {userRoles.length === 0 ? (
196
+ <Text className="text-slate-600 text-[10px] font-bold uppercase italic">
197
+ No Role
198
+ </Text>
199
+ ) : null}
200
+ </View>
201
+ </View>
202
+ </View>
203
+
204
+ <View className="flex-row items-center gap-2">
205
+ {togglingUser === item.id ? (
206
+ <ActivityIndicator size="small" color="#3b82f6" />
207
+ ) : (
208
+ <>
209
+ <TouchableOpacity
210
+ onPress={() => handleToggleRole(item.id, userRoles, "role_admin", "Admin")}
211
+ className={`px-3 py-1.5 rounded-lg border ${
212
+ isAdmin
213
+ ? "bg-red-500/10 border-red-500/20"
214
+ : "bg-blue-600 border-blue-500"
215
+ }`}
216
+ >
217
+ <Text className={`text-xs font-bold ${isAdmin ? "text-red-400" : "text-white"}`}>
218
+ {isAdmin ? "Remove Admin" : "Make Admin"}
219
+ </Text>
220
+ </TouchableOpacity>
221
+
222
+ <TouchableOpacity
223
+ onPress={() => handleToggleRole(item.id, userRoles, "role_user", "User")}
224
+ className={`px-3 py-1.5 rounded-lg border ${
225
+ isUser
226
+ ? "bg-red-500/10 border-red-500/20"
227
+ : "bg-slate-700 border-slate-600"
228
+ }`}
229
+ >
230
+ <Text className={`text-xs font-bold ${isUser ? "text-red-400" : "text-slate-300"}`}>
231
+ {isUser ? "Revoke User" : "Grant User"}
232
+ </Text>
233
+ </TouchableOpacity>
234
+ </>
235
+ )}
236
+ </View>
237
+ </Card>
238
+ </MotiView>
239
+ );
240
+ })}
241
+ </View>
242
+ </View>
243
+ </ScrollView>
244
+ );
245
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="nativewind/types" />
@@ -0,0 +1,21 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import AsyncStorage from "@react-native-async-storage/async-storage";
3
+ import { Platform } from "react-native";
4
+
5
+ // Resolve Hono API location dynamically based on Web vs Native environment
6
+ const API_URL = (Platform.select({
7
+ web: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8787",
8
+ default: "http://localhost:8787", // Dev server endpoint for local emulator testing
9
+ }) || "http://localhost:8787") as string;
10
+
11
+ export const authClient = Object.assign(
12
+ createAuthClient({
13
+ baseURL: API_URL,
14
+ storage: Platform.select({
15
+ web: undefined, // Standard browser cookies / local storage
16
+ default: AsyncStorage, // Persisted AsyncStorage state for React Native mobile builds
17
+ }),
18
+ }),
19
+ { baseURL: API_URL }
20
+ );
21
+
@@ -0,0 +1,19 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ "./src/**/*.{js,jsx,ts,tsx}",
5
+ "../next/app/**/*.{js,jsx,ts,tsx}",
6
+ "../expo/app/**/*.{js,jsx,ts,tsx}"
7
+ ],
8
+ presets: [require("nativewind/preset")],
9
+ theme: {
10
+ extend: {
11
+ colors: {
12
+ slate: {
13
+ 950: "#090d16"
14
+ }
15
+ }
16
+ },
17
+ },
18
+ plugins: [],
19
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@thunder/tsconfig/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "jsx": "react-jsx"
7
+ },
8
+ "include": ["src/**/*"]
9
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "thunder-monorepo",
3
+ "private": true,
4
+ "engines": {
5
+ "node": ">=22.0.0"
6
+ },
7
+ "scripts": {
8
+ "build": "dotenv -e .env -- turbo build",
9
+ "dev": "dotenv -e .env -- turbo dev",
10
+ "lint": "turbo lint",
11
+ "typecheck": "turbo typecheck",
12
+ "format": "prettier --write \"**/*.{ts,tsx,md}\"",
13
+ "format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
14
+ "db:generate": "dotenv -e .env -- turbo db:generate",
15
+ "db:migrate": "dotenv -e .env -- turbo db:migrate",
16
+ "db:seed": "dotenv -e .env -- turbo db:seed",
17
+ "test": "turbo run test:unit test",
18
+ "clean": "node scripts/clean.js",
19
+ "clean:install": "node scripts/clean.js && pnpm install"
20
+ },
21
+ "devDependencies": {
22
+ "dotenv-cli": "^7.4.4",
23
+ "prettier": "^3.9.4",
24
+ "turbo": "^2.10.4",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "packageManager": "pnpm@9.0.0",
28
+ "pnpm": {
29
+ "overrides": {
30
+ "esbuild": "0.25.12",
31
+ "sourcemap-codec": "npm:@jridgewell/sourcemap-codec@^1.5.0"
32
+ },
33
+ "peerDependencyRules": {
34
+ "ignoreMissing": [
35
+ "vercel"
36
+ ],
37
+ "allowAny": [
38
+ "react-native",
39
+ "react",
40
+ "next",
41
+ "drizzle-orm",
42
+ "drizzle-kit",
43
+ "vitest"
44
+ ]
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "target": "ES2022",
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "noUncheckedIndexedAccess": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true
19
+ }
20
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@thunder/tsconfig",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "files": [
6
+ "base.json"
7
+ ],
8
+ "exports": {
9
+ "./base.json": "./base.json"
10
+ }
11
+ }
@@ -0,0 +1,4 @@
1
+ packages:
2
+ - 'client/*'
3
+ - 'server/*'
4
+ - 'packages/*'
@@ -0,0 +1,56 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const targets = ['node_modules', 'dist', '.next', '.open-next', '.turbo', '.expo', 'web-build'];
5
+ const rootDir = path.resolve(__dirname, '..');
6
+
7
+ function cleanDir(dir) {
8
+ if (!fs.existsSync(dir)) return;
9
+
10
+ const files = fs.readdirSync(dir);
11
+ for (const file of files) {
12
+ const fullPath = path.join(dir, file);
13
+
14
+ if (targets.includes(file)) {
15
+ try {
16
+ const stats = fs.statSync(fullPath);
17
+ if (stats.isDirectory()) {
18
+ console.log(`Deleting: ${fullPath}`);
19
+ fs.rmSync(fullPath, { recursive: true, force: true });
20
+ }
21
+ } catch (err) {
22
+ console.error(`Failed to delete ${fullPath}:`, err.message);
23
+ }
24
+ } else {
25
+ try {
26
+ const stats = fs.statSync(fullPath);
27
+ if (stats.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
28
+ cleanDir(fullPath);
29
+ }
30
+ } catch (err) {
31
+ // ignore errors
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ console.log('Cleaning workspace installed and build files...');
38
+ cleanDir(rootDir);
39
+
40
+ // Clean root level targets as well
41
+ for (const target of targets) {
42
+ const rootTarget = path.join(rootDir, target);
43
+ if (fs.existsSync(rootTarget)) {
44
+ try {
45
+ const stats = fs.statSync(rootTarget);
46
+ if (stats.isDirectory()) {
47
+ console.log(`Deleting: ${rootTarget}`);
48
+ fs.rmSync(rootTarget, { recursive: true, force: true });
49
+ }
50
+ } catch (err) {
51
+ console.error(`Failed to delete ${rootTarget}:`, err.message);
52
+ }
53
+ }
54
+ }
55
+
56
+ console.log('Clean completed successfully!');
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ export default defineConfig({
4
+ schema: "./src/schema.ts",
5
+ out: "./migrations",
6
+ dialect: "postgresql",
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL!,
9
+ },
10
+ });
@@ -0,0 +1,86 @@
1
+ CREATE TABLE "account" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "account_id" text NOT NULL,
4
+ "provider_id" text NOT NULL,
5
+ "user_id" text NOT NULL,
6
+ "access_token" text,
7
+ "refresh_token" text,
8
+ "id_token" text,
9
+ "access_token_expires_at" timestamp,
10
+ "refresh_token_expires_at" timestamp,
11
+ "scope" text,
12
+ "password" text,
13
+ "created_at" timestamp NOT NULL,
14
+ "updated_at" timestamp NOT NULL
15
+ );
16
+ --> statement-breakpoint
17
+ CREATE TABLE "session" (
18
+ "id" text PRIMARY KEY NOT NULL,
19
+ "expires_at" timestamp NOT NULL,
20
+ "token" text NOT NULL,
21
+ "created_at" timestamp NOT NULL,
22
+ "updated_at" timestamp NOT NULL,
23
+ "ip_address" text,
24
+ "user_agent" text,
25
+ "user_id" text NOT NULL,
26
+ CONSTRAINT "session_token_unique" UNIQUE("token")
27
+ );
28
+ --> statement-breakpoint
29
+ CREATE TABLE "user" (
30
+ "id" text PRIMARY KEY NOT NULL,
31
+ "name" text NOT NULL,
32
+ "email" text NOT NULL,
33
+ "email_verified" boolean NOT NULL,
34
+ "image" text,
35
+ "created_at" timestamp NOT NULL,
36
+ "updated_at" timestamp NOT NULL,
37
+ CONSTRAINT "user_email_unique" UNIQUE("email")
38
+ );
39
+ --> statement-breakpoint
40
+ CREATE TABLE "verification" (
41
+ "id" text PRIMARY KEY NOT NULL,
42
+ "identifier" text NOT NULL,
43
+ "value" text NOT NULL,
44
+ "expires_at" timestamp NOT NULL,
45
+ "created_at" timestamp,
46
+ "updated_at" timestamp
47
+ );
48
+ --> statement-breakpoint
49
+ CREATE TABLE "permission" (
50
+ "id" text PRIMARY KEY NOT NULL,
51
+ "name" text NOT NULL,
52
+ "description" text,
53
+ "created_at" timestamp NOT NULL,
54
+ "updated_at" timestamp NOT NULL,
55
+ CONSTRAINT "permission_name_unique" UNIQUE("name")
56
+ );
57
+ --> statement-breakpoint
58
+ CREATE TABLE "role" (
59
+ "id" text PRIMARY KEY NOT NULL,
60
+ "name" text NOT NULL,
61
+ "description" text,
62
+ "created_at" timestamp NOT NULL,
63
+ "updated_at" timestamp NOT NULL,
64
+ CONSTRAINT "role_name_unique" UNIQUE("name")
65
+ );
66
+ --> statement-breakpoint
67
+ CREATE TABLE "role_permission" (
68
+ "id" text PRIMARY KEY NOT NULL,
69
+ "role_id" text NOT NULL,
70
+ "permission_id" text NOT NULL,
71
+ "created_at" timestamp NOT NULL
72
+ );
73
+ --> statement-breakpoint
74
+ CREATE TABLE "user_role" (
75
+ "id" text PRIMARY KEY NOT NULL,
76
+ "user_id" text NOT NULL,
77
+ "role_id" text NOT NULL,
78
+ "created_at" timestamp NOT NULL
79
+ );
80
+ --> statement-breakpoint
81
+ ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
82
+ ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
83
+ ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
84
+ ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_permission_id_permission_id_fk" FOREIGN KEY ("permission_id") REFERENCES "public"."permission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
85
+ ALTER TABLE "user_role" ADD CONSTRAINT "user_role_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
86
+ ALTER TABLE "user_role" ADD CONSTRAINT "user_role_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;