@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.
- package/index.js +229 -0
- package/package.json +26 -0
- package/postpublish.js +14 -0
- package/prepublish.js +68 -0
- package/template/.env.example +13 -0
- package/template/README.md +1 -0
- package/template/THUNDER_STACK.md +120 -0
- package/template/client/assets/logo.svg +36 -0
- package/template/client/expo/App.test.tsx +20 -0
- package/template/client/expo/App.tsx +16 -0
- package/template/client/expo/app.json +30 -0
- package/template/client/expo/babel.config.js +12 -0
- package/template/client/expo/global.css +3 -0
- package/template/client/expo/jest.config.js +7 -0
- package/template/client/expo/metro.config.js +21 -0
- package/template/client/expo/package.json +33 -0
- package/template/client/expo/tsconfig.json +6 -0
- package/template/client/next/app/globals.css +14 -0
- package/template/client/next/app/layout.tsx +28 -0
- package/template/client/next/app/page.test.tsx +13 -0
- package/template/client/next/app/page.tsx +10 -0
- package/template/client/next/next-env.d.ts +5 -0
- package/template/client/next/next.config.js +21 -0
- package/template/client/next/package.json +39 -0
- package/template/client/next/playwright.config.ts +17 -0
- package/template/client/next/postcss.config.js +6 -0
- package/template/client/next/tsconfig.json +30 -0
- package/template/client/next/vitest.config.ts +11 -0
- package/template/client/next/vitest.setup.ts +1 -0
- package/template/client/shared/index.ts +2 -0
- package/template/client/shared/package.json +24 -0
- package/template/client/shared/src/components/Button.tsx +38 -0
- package/template/client/shared/src/components/Card.tsx +17 -0
- package/template/client/shared/src/features/auth/login.tsx +139 -0
- package/template/client/shared/src/features/dashboard/index.tsx +232 -0
- package/template/client/shared/src/features/home/screen.tsx +212 -0
- package/template/client/shared/src/features/management/roles.tsx +356 -0
- package/template/client/shared/src/features/management/users.tsx +245 -0
- package/template/client/shared/src/nativewind-env.d.ts +1 -0
- package/template/client/shared/src/utils/auth.ts +21 -0
- package/template/client/shared/tailwind.config.js +19 -0
- package/template/client/shared/tsconfig.json +9 -0
- package/template/package.json +47 -0
- package/template/packages/tsconfig/base.json +20 -0
- package/template/packages/tsconfig/package.json +11 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/scripts/clean.js +56 -0
- package/template/server/db/drizzle.config.ts +10 -0
- package/template/server/db/migrations/0000_loving_mindworm.sql +86 -0
- package/template/server/db/migrations/meta/0000_snapshot.json +549 -0
- package/template/server/db/migrations/meta/_journal.json +13 -0
- package/template/server/db/package.json +28 -0
- package/template/server/db/src/index.ts +16 -0
- package/template/server/db/src/migrate.ts +28 -0
- package/template/server/db/src/schema/auth.ts +73 -0
- package/template/server/db/src/schema/index.ts +2 -0
- package/template/server/db/src/schema/rbac.ts +72 -0
- package/template/server/db/src/schema.ts +1 -0
- package/template/server/db/src/seed.ts +204 -0
- package/template/server/db/src/services/rbac.ts +144 -0
- package/template/server/db/src/services/user.ts +34 -0
- package/template/server/db/src/types/index.ts +34 -0
- package/template/server/db/tsconfig.json +8 -0
- package/template/server/hono/package.json +22 -0
- package/template/server/hono/src/auth.ts +29 -0
- package/template/server/hono/src/index.ts +215 -0
- package/template/server/hono/tsconfig.json +8 -0
- package/template/server/hono/wrangler.toml +12 -0
- 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,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,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,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;
|