create-workerstack 0.1.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/bin/index.js +918 -0
- package/package.json +25 -0
- package/template/.env.example +6 -0
- package/template/better-auth.config.ts +23 -0
- package/template/drizzle.config.ts +10 -0
- package/template/package.json +40 -0
- package/template/public/.assetsignore +1 -0
- package/template/public/favicon.ico +0 -0
- package/template/src/api/index.ts +22 -0
- package/template/src/api/middlewares/auth.ts +15 -0
- package/template/src/client/App.tsx +316 -0
- package/template/src/client/main.tsx +9 -0
- package/template/src/client/tsconfig.json +17 -0
- package/template/src/client.tsx +36 -0
- package/template/src/database/auth-schema.ts +98 -0
- package/template/src/database/db.ts +111 -0
- package/template/src/database/schema.ts +1 -0
- package/template/src/index.tsx +17 -0
- package/template/src/lib/better-auth/index.ts +28 -0
- package/template/src/lib/better-auth/options.ts +29 -0
- package/template/src/services/email.ts +51 -0
- package/template/src/style.css +45 -0
- package/template/src/utils/logger.ts +211 -0
- package/template/src/utils/noCache.ts +20 -0
- package/template/tsconfig.json +18 -0
- package/template/vite.config.ts +34 -0
- package/template/worker-configuration.d.ts +12049 -0
- package/template/wrangler.jsonc +7 -0
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-workerstack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a full-stack Cloudflare Workers app with Hono, React, MUI, Better Auth, and Drizzle",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-workerstack": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "bun build src/index.ts --target node --outfile bin/index.js",
|
|
15
|
+
"prepublishOnly": "rm -rf template && cp -r ../template ./template && bun run build"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@clack/prompts": "^0.9.1",
|
|
19
|
+
"picocolors": "^1.1.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
3
|
+
import { betterAuthOptions } from '@/lib/better-auth/options';
|
|
4
|
+
import { initDB } from "@/database/db";
|
|
5
|
+
|
|
6
|
+
import * as schema from "@/database/schema";
|
|
7
|
+
|
|
8
|
+
const db = initDB(process.env.DATABASE_URL!)
|
|
9
|
+
const env = process.env;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Better Auth Instance
|
|
14
|
+
*/
|
|
15
|
+
export const auth = betterAuth({
|
|
16
|
+
...betterAuthOptions(env as unknown as CloudflareBindings),
|
|
17
|
+
database: drizzleAdapter(db, {
|
|
18
|
+
provider: "pg",
|
|
19
|
+
schema,
|
|
20
|
+
}),
|
|
21
|
+
baseURL: env.BETTER_AUTH_URL!,
|
|
22
|
+
secret: env.BETTER_AUTH_SECRET!,
|
|
23
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"description": "{{description}}",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "$npm_execpath run build && vite preview",
|
|
9
|
+
"deploy": "$npm_execpath run build && wrangler deploy",
|
|
10
|
+
"deploy:dev": "CLOUDFLARE_ENV=dev $npm_execpath run build && wrangler deploy",
|
|
11
|
+
"cf-typegen": "wrangler types --env-interface CloudflareBindings",
|
|
12
|
+
"typecheck:client": "tsc --noEmit -p src/client/tsconfig.json",
|
|
13
|
+
"typecheck:server": "tsc --noEmit",
|
|
14
|
+
"db:generate": "drizzle-kit generate",
|
|
15
|
+
"db:migrate": "bunx drizzle-kit migrate",
|
|
16
|
+
"ba:schema": "bun x auth@latest generate --config better-auth.config.ts --output src/database/auth-schema.ts"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@mui/material": "^7.3.9",
|
|
20
|
+
"better-auth": "^1.5.5",
|
|
21
|
+
"drizzle-orm": "^0.45.1",
|
|
22
|
+
"hono": "^4.12.8",
|
|
23
|
+
"postgres": "^3.4.8",
|
|
24
|
+
"react": "^19.2.4",
|
|
25
|
+
"react-dom": "^19.2.4",
|
|
26
|
+
"resend": "^6.9.4"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@cloudflare/vite-plugin": "^1.2.3",
|
|
30
|
+
"@types/bun": "^1.3.11",
|
|
31
|
+
"@types/react": "^19.2.14",
|
|
32
|
+
"@types/react-dom": "^19.2.3",
|
|
33
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
34
|
+
"drizzle-kit": "^0.31.10",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"vite": "^8",
|
|
37
|
+
"vite-ssr-components": "^0.5.2",
|
|
38
|
+
"wrangler": "^4.17.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.vite
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { auth, type AuthType } from '@/lib/better-auth'
|
|
3
|
+
import { authMiddleware } from './middlewares/auth'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const server = new Hono<{ Bindings: CloudflareBindings, Variables: AuthType }>()
|
|
7
|
+
|
|
8
|
+
server.on(['POST', 'GET'], '/auth/*', (c) => {
|
|
9
|
+
return auth(c.var.db, c.env).handler(c.req.raw);
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
server.use('*', authMiddleware)
|
|
13
|
+
|
|
14
|
+
server.get('/health', (c) => {
|
|
15
|
+
return c.json({ status: 'ok' })
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
server.all('/*', (c) => {
|
|
19
|
+
return c.json({ error: 'Not Found' }, 404)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export default server
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createMiddleware } from 'hono/factory'
|
|
2
|
+
import { auth, type AuthType } from '@/lib/better-auth'
|
|
3
|
+
|
|
4
|
+
export const authMiddleware = createMiddleware<{ Bindings: CloudflareBindings, Variables: AuthType }>(async (c, next) => {
|
|
5
|
+
const authInstance = auth(c.var.db, c.env)
|
|
6
|
+
|
|
7
|
+
const session = await authInstance.api.getSession({
|
|
8
|
+
headers: c.req.raw.headers,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
c.set('user', session?.user ?? null)
|
|
12
|
+
c.set('session', session?.session ?? null)
|
|
13
|
+
|
|
14
|
+
await next()
|
|
15
|
+
})
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ThemeProvider,
|
|
3
|
+
createTheme,
|
|
4
|
+
CssBaseline,
|
|
5
|
+
Container,
|
|
6
|
+
Typography,
|
|
7
|
+
Box,
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
Chip,
|
|
11
|
+
Stack,
|
|
12
|
+
Grid,
|
|
13
|
+
} from '@mui/material';
|
|
14
|
+
|
|
15
|
+
const darkTheme = createTheme({
|
|
16
|
+
palette: {
|
|
17
|
+
mode: 'dark',
|
|
18
|
+
background: { default: '#0A0A0F', paper: '#12121A' },
|
|
19
|
+
text: { primary: '#F1F5F9', secondary: '#94A3B8' },
|
|
20
|
+
primary: { main: '#7C3AED' },
|
|
21
|
+
},
|
|
22
|
+
typography: {
|
|
23
|
+
fontFamily: "'Plus Jakarta Sans', sans-serif",
|
|
24
|
+
h1: { fontFamily: "'Sora', sans-serif" },
|
|
25
|
+
h2: { fontFamily: "'Sora', sans-serif" },
|
|
26
|
+
},
|
|
27
|
+
shape: { borderRadius: 12 },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const stackItems = [
|
|
31
|
+
{
|
|
32
|
+
name: 'Cloudflare Workers',
|
|
33
|
+
description: 'Edge-first serverless runtime. Your code runs in 300+ locations worldwide.',
|
|
34
|
+
icon: '\u2601\uFE0F',
|
|
35
|
+
url: 'https://developers.cloudflare.com/workers/',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'Hono',
|
|
39
|
+
description: 'Ultrafast web framework built for the edge. Handles routing, middleware, and more.',
|
|
40
|
+
icon: '\uD83D\uDD25',
|
|
41
|
+
url: 'https://hono.dev/',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'React 19',
|
|
45
|
+
description: 'The library for building user interfaces. Server and client rendering ready.',
|
|
46
|
+
icon: '\u269B\uFE0F',
|
|
47
|
+
url: 'https://react.dev/',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'MUI Material',
|
|
51
|
+
description: 'Production-grade React component library with a comprehensive design system.',
|
|
52
|
+
icon: '\uD83C\uDFA8',
|
|
53
|
+
url: 'https://mui.com/',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'Better Auth',
|
|
57
|
+
description: 'Authentication for modern apps. Email/password, OAuth, sessions, and admin.',
|
|
58
|
+
icon: '\uD83D\uDD10',
|
|
59
|
+
url: 'https://www.better-auth.com/',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Drizzle ORM',
|
|
63
|
+
description: 'Type-safe SQL ORM with zero dependencies. Migrations and schema management built-in.',
|
|
64
|
+
icon: '\uD83D\uDDC4\uFE0F',
|
|
65
|
+
url: 'https://orm.drizzle.team/',
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const quickStartSteps = [
|
|
70
|
+
{ cmd: 'src/client/App.tsx', desc: 'Edit this file to start building your UI' },
|
|
71
|
+
{ cmd: 'src/api/index.ts', desc: 'Add your API routes here' },
|
|
72
|
+
{ cmd: 'bun run dev', desc: 'Start the development server' },
|
|
73
|
+
{ cmd: 'bun run deploy', desc: 'Deploy to Cloudflare Workers' },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function App() {
|
|
77
|
+
return (
|
|
78
|
+
<ThemeProvider theme={darkTheme}>
|
|
79
|
+
<CssBaseline />
|
|
80
|
+
<Box
|
|
81
|
+
sx={{
|
|
82
|
+
minHeight: '100vh',
|
|
83
|
+
position: 'relative',
|
|
84
|
+
overflow: 'hidden',
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{/* Background glow */}
|
|
88
|
+
<Box
|
|
89
|
+
sx={{
|
|
90
|
+
position: 'absolute',
|
|
91
|
+
top: '-20%',
|
|
92
|
+
left: '50%',
|
|
93
|
+
transform: 'translateX(-50%)',
|
|
94
|
+
width: '600px',
|
|
95
|
+
height: '600px',
|
|
96
|
+
borderRadius: '50%',
|
|
97
|
+
background: 'radial-gradient(circle, rgba(124,58,237,0.15) 0%, transparent 70%)',
|
|
98
|
+
filter: 'blur(80px)',
|
|
99
|
+
pointerEvents: 'none',
|
|
100
|
+
animation: 'pulse 6s ease-in-out infinite',
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<Container maxWidth="md" sx={{ position: 'relative', py: 8 }}>
|
|
105
|
+
{/* Hero */}
|
|
106
|
+
<Box
|
|
107
|
+
sx={{
|
|
108
|
+
textAlign: 'center',
|
|
109
|
+
mb: 8,
|
|
110
|
+
animation: 'fadeInUp 0.8s ease-out',
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<Typography
|
|
114
|
+
variant="h1"
|
|
115
|
+
sx={{
|
|
116
|
+
fontSize: { xs: '2.5rem', md: '4rem' },
|
|
117
|
+
fontWeight: 800,
|
|
118
|
+
background: 'linear-gradient(135deg, #7C3AED 0%, #A78BFA 50%, #C4B5FD 100%)',
|
|
119
|
+
backgroundClip: 'text',
|
|
120
|
+
WebkitBackgroundClip: 'text',
|
|
121
|
+
WebkitTextFillColor: 'transparent',
|
|
122
|
+
mb: 2,
|
|
123
|
+
letterSpacing: '-0.02em',
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
WorkerStack
|
|
127
|
+
</Typography>
|
|
128
|
+
<Typography
|
|
129
|
+
variant="h5"
|
|
130
|
+
sx={{
|
|
131
|
+
color: 'text.secondary',
|
|
132
|
+
fontWeight: 400,
|
|
133
|
+
maxWidth: '500px',
|
|
134
|
+
mx: 'auto',
|
|
135
|
+
lineHeight: 1.6,
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
Full-stack Cloudflare Workers starter with everything you need to ship fast.
|
|
139
|
+
</Typography>
|
|
140
|
+
</Box>
|
|
141
|
+
|
|
142
|
+
{/* Stack Grid */}
|
|
143
|
+
<Box sx={{ mb: 8 }}>
|
|
144
|
+
<Grid container spacing={2}>
|
|
145
|
+
{stackItems.map((item) => (
|
|
146
|
+
<Grid size={{ xs: 12, sm: 6 }} key={item.name}>
|
|
147
|
+
<Card
|
|
148
|
+
component="a"
|
|
149
|
+
href={item.url}
|
|
150
|
+
target="_blank"
|
|
151
|
+
rel="noopener noreferrer"
|
|
152
|
+
sx={{
|
|
153
|
+
height: '100%',
|
|
154
|
+
display: 'flex',
|
|
155
|
+
bgcolor: 'background.paper',
|
|
156
|
+
border: '1px solid',
|
|
157
|
+
borderColor: 'rgba(124,58,237,0.15)',
|
|
158
|
+
textDecoration: 'none',
|
|
159
|
+
transition: 'all 0.2s ease',
|
|
160
|
+
'&:hover': {
|
|
161
|
+
borderColor: 'primary.main',
|
|
162
|
+
transform: 'translateY(-2px)',
|
|
163
|
+
boxShadow: '0 8px 30px rgba(124,58,237,0.15)',
|
|
164
|
+
},
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
<CardContent sx={{ p: 3 }}>
|
|
168
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 1 }}>
|
|
169
|
+
<Typography sx={{ fontSize: '1.5rem' }}>{item.icon}</Typography>
|
|
170
|
+
<Typography variant="h6" sx={{ fontWeight: 700, color: 'text.primary' }}>
|
|
171
|
+
{item.name}
|
|
172
|
+
</Typography>
|
|
173
|
+
</Box>
|
|
174
|
+
<Typography variant="body2" sx={{ color: 'text.secondary', lineHeight: 1.6 }}>
|
|
175
|
+
{item.description}
|
|
176
|
+
</Typography>
|
|
177
|
+
</CardContent>
|
|
178
|
+
</Card>
|
|
179
|
+
</Grid>
|
|
180
|
+
))}
|
|
181
|
+
</Grid>
|
|
182
|
+
</Box>
|
|
183
|
+
|
|
184
|
+
{/* Quick Start */}
|
|
185
|
+
<Box
|
|
186
|
+
sx={{
|
|
187
|
+
mb: 8,
|
|
188
|
+
animation: 'fadeInUp 0.8s ease-out 0.2s both',
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
<Typography
|
|
192
|
+
variant="h5"
|
|
193
|
+
sx={{
|
|
194
|
+
fontWeight: 700,
|
|
195
|
+
mb: 3,
|
|
196
|
+
textAlign: 'center',
|
|
197
|
+
fontFamily: "'Sora', sans-serif",
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
Get started
|
|
201
|
+
</Typography>
|
|
202
|
+
<Box
|
|
203
|
+
sx={{
|
|
204
|
+
bgcolor: '#0D0D14',
|
|
205
|
+
border: '1px solid rgba(124,58,237,0.2)',
|
|
206
|
+
borderRadius: 2,
|
|
207
|
+
overflow: 'hidden',
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{/* Terminal header */}
|
|
211
|
+
<Box
|
|
212
|
+
sx={{
|
|
213
|
+
px: 2,
|
|
214
|
+
py: 1.5,
|
|
215
|
+
borderBottom: '1px solid rgba(255,255,255,0.06)',
|
|
216
|
+
display: 'flex',
|
|
217
|
+
gap: 1,
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
<Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: '#FF5F57' }} />
|
|
221
|
+
<Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: '#FFBD2E' }} />
|
|
222
|
+
<Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: '#28C840' }} />
|
|
223
|
+
</Box>
|
|
224
|
+
{/* Terminal content */}
|
|
225
|
+
<Box sx={{ p: 3 }}>
|
|
226
|
+
{quickStartSteps.map((step, i) => (
|
|
227
|
+
<Box key={i} sx={{ mb: i < quickStartSteps.length - 1 ? 2 : 0 }}>
|
|
228
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
229
|
+
<Typography
|
|
230
|
+
component="span"
|
|
231
|
+
sx={{ color: '#7C3AED', fontFamily: 'monospace', fontWeight: 700 }}
|
|
232
|
+
>
|
|
233
|
+
{'\u276F'}
|
|
234
|
+
</Typography>
|
|
235
|
+
<Typography
|
|
236
|
+
component="span"
|
|
237
|
+
sx={{ color: '#C4B5FD', fontFamily: 'monospace', fontSize: '0.95rem' }}
|
|
238
|
+
>
|
|
239
|
+
{step.cmd}
|
|
240
|
+
</Typography>
|
|
241
|
+
</Box>
|
|
242
|
+
<Typography
|
|
243
|
+
sx={{
|
|
244
|
+
color: 'text.secondary',
|
|
245
|
+
fontSize: '0.85rem',
|
|
246
|
+
pl: 3,
|
|
247
|
+
mt: 0.3,
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
{step.desc}
|
|
251
|
+
</Typography>
|
|
252
|
+
</Box>
|
|
253
|
+
))}
|
|
254
|
+
</Box>
|
|
255
|
+
</Box>
|
|
256
|
+
</Box>
|
|
257
|
+
|
|
258
|
+
{/* Doc Links */}
|
|
259
|
+
<Box sx={{ textAlign: 'center', mb: 6 }}>
|
|
260
|
+
<Stack
|
|
261
|
+
direction="row"
|
|
262
|
+
spacing={1}
|
|
263
|
+
flexWrap="wrap"
|
|
264
|
+
justifyContent="center"
|
|
265
|
+
useFlexGap
|
|
266
|
+
sx={{ gap: 1 }}
|
|
267
|
+
>
|
|
268
|
+
{stackItems.map((item) => (
|
|
269
|
+
<Chip
|
|
270
|
+
key={item.name}
|
|
271
|
+
label={item.name}
|
|
272
|
+
component="a"
|
|
273
|
+
href={item.url}
|
|
274
|
+
target="_blank"
|
|
275
|
+
rel="noopener noreferrer"
|
|
276
|
+
clickable
|
|
277
|
+
sx={{
|
|
278
|
+
bgcolor: 'rgba(124,58,237,0.1)',
|
|
279
|
+
border: '1px solid rgba(124,58,237,0.2)',
|
|
280
|
+
color: '#C4B5FD',
|
|
281
|
+
fontWeight: 500,
|
|
282
|
+
'&:hover': {
|
|
283
|
+
bgcolor: 'rgba(124,58,237,0.2)',
|
|
284
|
+
},
|
|
285
|
+
}}
|
|
286
|
+
/>
|
|
287
|
+
))}
|
|
288
|
+
</Stack>
|
|
289
|
+
</Box>
|
|
290
|
+
|
|
291
|
+
{/* Footer */}
|
|
292
|
+
<Box sx={{ textAlign: 'center', pb: 4 }}>
|
|
293
|
+
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
294
|
+
Built with{' '}
|
|
295
|
+
<Typography
|
|
296
|
+
component="span"
|
|
297
|
+
variant="body2"
|
|
298
|
+
sx={{
|
|
299
|
+
background: 'linear-gradient(135deg, #7C3AED, #A78BFA)',
|
|
300
|
+
backgroundClip: 'text',
|
|
301
|
+
WebkitBackgroundClip: 'text',
|
|
302
|
+
WebkitTextFillColor: 'transparent',
|
|
303
|
+
fontWeight: 600,
|
|
304
|
+
}}
|
|
305
|
+
>
|
|
306
|
+
WorkerStack
|
|
307
|
+
</Typography>
|
|
308
|
+
</Typography>
|
|
309
|
+
</Box>
|
|
310
|
+
</Container>
|
|
311
|
+
</Box>
|
|
312
|
+
</ThemeProvider>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default App;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"jsxImportSource": "react",
|
|
11
|
+
"types": ["vite/client", "react", "react-dom", "@mui/material"],
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": ["../*"],
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"include": ["./**/*"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsxRenderer } from 'hono/jsx-renderer'
|
|
2
|
+
import { Script, Link, ViteClient } from 'vite-ssr-components/hono'
|
|
3
|
+
|
|
4
|
+
export const renderer = jsxRenderer(({ children }) => {
|
|
5
|
+
return (
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
|
+
<ViteClient />
|
|
11
|
+
{!import.meta.env.PROD && (
|
|
12
|
+
<script type="module" dangerouslySetInnerHTML={{ __html: `
|
|
13
|
+
import RefreshRuntime from '/@react-refresh'
|
|
14
|
+
RefreshRuntime.injectIntoGlobalHook(window)
|
|
15
|
+
window.$RefreshReg$ = () => {}
|
|
16
|
+
window.$RefreshSig$ = () => (type) => type
|
|
17
|
+
window.__vite_plugin_react_preamble_installed__ = true
|
|
18
|
+
` }} />
|
|
19
|
+
)}
|
|
20
|
+
<Script src="/src/client/main.tsx" />
|
|
21
|
+
<Link href="/src/style.css" rel="stylesheet" />
|
|
22
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
23
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
24
|
+
<link
|
|
25
|
+
rel="stylesheet"
|
|
26
|
+
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Sora:wght@600;700;800&display=swap"
|
|
27
|
+
/>
|
|
28
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
29
|
+
<title>{'{{projectName}}'}</title>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
{children}
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
)
|
|
36
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { relations } from "drizzle-orm";
|
|
2
|
+
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
|
|
3
|
+
|
|
4
|
+
export const user = pgTable("user", {
|
|
5
|
+
id: text("id").primaryKey(),
|
|
6
|
+
name: text("name").notNull(),
|
|
7
|
+
email: text("email").notNull().unique(),
|
|
8
|
+
emailVerified: boolean("email_verified").default(false).notNull(),
|
|
9
|
+
image: text("image"),
|
|
10
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
11
|
+
updatedAt: timestamp("updated_at")
|
|
12
|
+
.defaultNow()
|
|
13
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
14
|
+
.notNull(),
|
|
15
|
+
role: text("role"),
|
|
16
|
+
banned: boolean("banned").default(false),
|
|
17
|
+
banReason: text("ban_reason"),
|
|
18
|
+
banExpires: timestamp("ban_expires"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const session = pgTable(
|
|
22
|
+
"session",
|
|
23
|
+
{
|
|
24
|
+
id: text("id").primaryKey(),
|
|
25
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
26
|
+
token: text("token").notNull().unique(),
|
|
27
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
28
|
+
updatedAt: timestamp("updated_at")
|
|
29
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
30
|
+
.notNull(),
|
|
31
|
+
ipAddress: text("ip_address"),
|
|
32
|
+
userAgent: text("user_agent"),
|
|
33
|
+
userId: text("user_id")
|
|
34
|
+
.notNull()
|
|
35
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
36
|
+
impersonatedBy: text("impersonated_by"),
|
|
37
|
+
},
|
|
38
|
+
(table) => [index("session_userId_idx").on(table.userId)],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export const account = pgTable(
|
|
42
|
+
"account",
|
|
43
|
+
{
|
|
44
|
+
id: text("id").primaryKey(),
|
|
45
|
+
accountId: text("account_id").notNull(),
|
|
46
|
+
providerId: text("provider_id").notNull(),
|
|
47
|
+
userId: text("user_id")
|
|
48
|
+
.notNull()
|
|
49
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
50
|
+
accessToken: text("access_token"),
|
|
51
|
+
refreshToken: text("refresh_token"),
|
|
52
|
+
idToken: text("id_token"),
|
|
53
|
+
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
|
54
|
+
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
55
|
+
scope: text("scope"),
|
|
56
|
+
password: text("password"),
|
|
57
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
58
|
+
updatedAt: timestamp("updated_at")
|
|
59
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
60
|
+
.notNull(),
|
|
61
|
+
},
|
|
62
|
+
(table) => [index("account_userId_idx").on(table.userId)],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export const verification = pgTable(
|
|
66
|
+
"verification",
|
|
67
|
+
{
|
|
68
|
+
id: text("id").primaryKey(),
|
|
69
|
+
identifier: text("identifier").notNull(),
|
|
70
|
+
value: text("value").notNull(),
|
|
71
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
72
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
73
|
+
updatedAt: timestamp("updated_at")
|
|
74
|
+
.defaultNow()
|
|
75
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
76
|
+
.notNull(),
|
|
77
|
+
},
|
|
78
|
+
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const userRelations = relations(user, ({ many }) => ({
|
|
82
|
+
sessions: many(session),
|
|
83
|
+
accounts: many(account),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
export const sessionRelations = relations(session, ({ one }) => ({
|
|
87
|
+
user: one(user, {
|
|
88
|
+
fields: [session.userId],
|
|
89
|
+
references: [user.id],
|
|
90
|
+
}),
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
export const accountRelations = relations(account, ({ one }) => ({
|
|
94
|
+
user: one(user, {
|
|
95
|
+
fields: [account.userId],
|
|
96
|
+
references: [user.id],
|
|
97
|
+
}),
|
|
98
|
+
}));
|