create-kuckit-app 0.1.1 → 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/dist/bin.js +1 -1
- package/dist/{create-project-DTm05G7D.js → create-project-CP-h4Ygi.js} +7 -5
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/templates/base/.claude/CLAUDE.md +44 -0
- package/templates/base/.claude/agents/daidalos.md +76 -0
- package/templates/base/.claude/agents/episteme.md +79 -0
- package/templates/base/.claude/agents/librarian.md +132 -0
- package/templates/base/.claude/agents/oracle.md +210 -0
- package/templates/base/.claude/commands/create-plan.md +159 -0
- package/templates/base/.claude/commands/file-beads.md +98 -0
- package/templates/base/.claude/commands/review-beads.md +161 -0
- package/templates/base/.claude/settings.json +11 -0
- package/templates/base/.claude/skills/kuckit/SKILL.md +436 -0
- package/templates/base/.claude/skills/kuckit/references/ARCHITECTURE.md +388 -0
- package/templates/base/.claude/skills/kuckit/references/CLI-COMMANDS.md +365 -0
- package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +581 -0
- package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +112 -0
- package/templates/base/.claude/skills/kuckit/references/PUBLISHING.md +231 -0
- package/templates/base/.env.example +13 -0
- package/templates/base/.github/workflows/ci.yml +28 -0
- package/templates/base/.husky/pre-commit +1 -0
- package/templates/base/.prettierignore +5 -0
- package/templates/base/.prettierrc +8 -0
- package/templates/base/AGENTS.md +148 -0
- package/templates/base/apps/server/.env.example +18 -0
- package/templates/base/apps/server/AGENTS.md +37 -0
- package/templates/base/apps/server/package.json +13 -2
- package/templates/base/apps/server/src/app.ts +20 -0
- package/templates/base/apps/server/src/auth.ts +10 -0
- package/templates/base/apps/server/src/config/modules.ts +22 -0
- package/templates/base/apps/server/src/container.ts +81 -0
- package/templates/base/apps/server/src/health.ts +27 -0
- package/templates/base/apps/server/src/middleware/container.ts +41 -0
- package/templates/base/apps/server/src/rpc-router-registry.ts +26 -0
- package/templates/base/apps/server/src/rpc.ts +31 -0
- package/templates/base/apps/server/src/server.ts +42 -14
- package/templates/base/apps/web/.env.example +4 -0
- package/templates/base/apps/web/AGENTS.md +53 -0
- package/templates/base/apps/web/index.html +1 -1
- package/templates/base/apps/web/package.json +15 -2
- package/templates/base/apps/web/src/lib/kuckit-router.ts +42 -0
- package/templates/base/apps/web/src/main.tsx +26 -14
- package/templates/base/apps/web/src/providers/KuckitProvider.tsx +147 -0
- package/templates/base/apps/web/src/providers/ServicesProvider.tsx +47 -0
- package/templates/base/apps/web/src/routeTree.gen.ts +91 -0
- package/templates/base/apps/web/src/routes/__root.tsx +31 -0
- package/templates/base/apps/web/src/routes/index.tsx +46 -0
- package/templates/base/apps/web/src/routes/login.tsx +108 -0
- package/templates/base/apps/web/src/services/auth-client.ts +12 -0
- package/templates/base/apps/web/src/services/index.ts +3 -0
- package/templates/base/apps/web/src/services/rpc.ts +29 -0
- package/templates/base/apps/web/src/services/types.ts +14 -0
- package/templates/base/apps/web/vite.config.ts +2 -1
- package/templates/base/docker-compose.yml +23 -0
- package/templates/base/eslint.config.js +18 -0
- package/templates/base/package.json +32 -2
- package/templates/base/packages/api/AGENTS.md +27 -0
- package/templates/base/packages/api/package.json +35 -0
- package/templates/base/packages/api/src/context.ts +48 -0
- package/templates/base/packages/api/src/index.ts +22 -0
- package/templates/base/packages/api/tsconfig.json +8 -0
- package/templates/base/packages/auth/AGENTS.md +45 -0
- package/templates/base/packages/auth/package.json +27 -0
- package/templates/base/packages/auth/src/index.ts +22 -0
- package/templates/base/packages/auth/tsconfig.json +8 -0
- package/templates/base/packages/db/AGENTS.md +59 -0
- package/templates/base/packages/db/drizzle.config.ts +19 -0
- package/templates/base/packages/db/package.json +36 -0
- package/templates/base/packages/db/src/connection.ts +40 -0
- package/templates/base/packages/db/src/index.ts +4 -0
- package/templates/base/packages/db/src/migrations/0000_init.sql +54 -0
- package/templates/base/packages/db/src/migrations/meta/_journal.json +13 -0
- package/templates/base/packages/db/src/schema/auth.ts +51 -0
- package/templates/base/packages/db/tsconfig.json +8 -0
- package/templates/base/packages/items-module/AGENTS.md +112 -0
- package/templates/base/packages/items-module/package.json +32 -0
- package/templates/base/packages/items-module/src/adapters/item.drizzle.ts +66 -0
- package/templates/base/packages/items-module/src/api/items.router.ts +47 -0
- package/templates/base/packages/items-module/src/client-module.ts +39 -0
- package/templates/base/packages/items-module/src/domain/item.entity.ts +36 -0
- package/templates/base/packages/items-module/src/index.ts +15 -0
- package/templates/base/packages/items-module/src/module.ts +53 -0
- package/templates/base/packages/items-module/src/ports/item.repository.ts +13 -0
- package/templates/base/packages/items-module/src/ui/ItemsPage.tsx +162 -0
- package/templates/base/packages/items-module/src/usecases/create-item.ts +25 -0
- package/templates/base/packages/items-module/src/usecases/delete-item.ts +18 -0
- package/templates/base/packages/items-module/src/usecases/get-item.ts +19 -0
- package/templates/base/packages/items-module/src/usecases/list-items.ts +21 -0
- package/templates/base/packages/items-module/tsconfig.json +9 -0
- package/templates/base/turbo.json +13 -1
- package/templates/base/apps/web/src/App.tsx +0 -16
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
+
import { useServices } from '../providers/ServicesProvider'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/')({
|
|
5
|
+
component: HomePage,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
function HomePage() {
|
|
9
|
+
const { authClient } = useServices()
|
|
10
|
+
const { data: session, isPending } = authClient.useSession()
|
|
11
|
+
|
|
12
|
+
if (isPending) {
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
|
|
15
|
+
<p>Loading...</p>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
|
|
22
|
+
<h1>Welcome to __APP_NAME__</h1>
|
|
23
|
+
{session?.user ? (
|
|
24
|
+
<div>
|
|
25
|
+
<p>Hello, {session.user.name || session.user.email}!</p>
|
|
26
|
+
<button
|
|
27
|
+
onClick={() => authClient.signOut()}
|
|
28
|
+
style={{
|
|
29
|
+
padding: '0.5rem 1rem',
|
|
30
|
+
cursor: 'pointer',
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
Sign Out
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
) : (
|
|
37
|
+
<div>
|
|
38
|
+
<p>Your Kuckit application is ready!</p>
|
|
39
|
+
<Link to="/login" style={{ color: 'blue', textDecoration: 'underline' }}>
|
|
40
|
+
Sign In
|
|
41
|
+
</Link>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { useServices } from '../providers/ServicesProvider'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('/login')({
|
|
6
|
+
component: LoginPage,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
function LoginPage() {
|
|
10
|
+
const { authClient } = useServices()
|
|
11
|
+
const navigate = useNavigate()
|
|
12
|
+
const [isSignUp, setIsSignUp] = useState(false)
|
|
13
|
+
const [email, setEmail] = useState('')
|
|
14
|
+
const [password, setPassword] = useState('')
|
|
15
|
+
const [name, setName] = useState('')
|
|
16
|
+
const [error, setError] = useState('')
|
|
17
|
+
const [loading, setLoading] = useState(false)
|
|
18
|
+
|
|
19
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
20
|
+
e.preventDefault()
|
|
21
|
+
setError('')
|
|
22
|
+
setLoading(true)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (isSignUp) {
|
|
26
|
+
await authClient.signUp.email({
|
|
27
|
+
email,
|
|
28
|
+
password,
|
|
29
|
+
name,
|
|
30
|
+
})
|
|
31
|
+
} else {
|
|
32
|
+
await authClient.signIn.email({
|
|
33
|
+
email,
|
|
34
|
+
password,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
navigate({ to: '/' })
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setError(err instanceof Error ? err.message : 'Authentication failed')
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui', maxWidth: '400px', margin: '0 auto' }}>
|
|
47
|
+
<h1>{isSignUp ? 'Create Account' : 'Sign In'}</h1>
|
|
48
|
+
|
|
49
|
+
<form
|
|
50
|
+
onSubmit={handleSubmit}
|
|
51
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
|
|
52
|
+
>
|
|
53
|
+
{isSignUp && (
|
|
54
|
+
<input
|
|
55
|
+
type="text"
|
|
56
|
+
placeholder="Name"
|
|
57
|
+
value={name}
|
|
58
|
+
onChange={(e) => setName(e.target.value)}
|
|
59
|
+
required
|
|
60
|
+
style={{ padding: '0.5rem', fontSize: '1rem' }}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
<input
|
|
64
|
+
type="email"
|
|
65
|
+
placeholder="Email"
|
|
66
|
+
value={email}
|
|
67
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
68
|
+
required
|
|
69
|
+
style={{ padding: '0.5rem', fontSize: '1rem' }}
|
|
70
|
+
/>
|
|
71
|
+
<input
|
|
72
|
+
type="password"
|
|
73
|
+
placeholder="Password"
|
|
74
|
+
value={password}
|
|
75
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
76
|
+
required
|
|
77
|
+
style={{ padding: '0.5rem', fontSize: '1rem' }}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
81
|
+
|
|
82
|
+
<button
|
|
83
|
+
type="submit"
|
|
84
|
+
disabled={loading}
|
|
85
|
+
style={{ padding: '0.5rem 1rem', fontSize: '1rem', cursor: 'pointer' }}
|
|
86
|
+
>
|
|
87
|
+
{loading ? 'Loading...' : isSignUp ? 'Create Account' : 'Sign In'}
|
|
88
|
+
</button>
|
|
89
|
+
</form>
|
|
90
|
+
|
|
91
|
+
<p style={{ marginTop: '1rem' }}>
|
|
92
|
+
{isSignUp ? 'Already have an account? ' : "Don't have an account? "}
|
|
93
|
+
<button
|
|
94
|
+
onClick={() => setIsSignUp(!isSignUp)}
|
|
95
|
+
style={{
|
|
96
|
+
background: 'none',
|
|
97
|
+
border: 'none',
|
|
98
|
+
color: 'blue',
|
|
99
|
+
cursor: 'pointer',
|
|
100
|
+
textDecoration: 'underline',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
{isSignUp ? 'Sign In' : 'Create Account'}
|
|
104
|
+
</button>
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { auth } from '@__APP_NAME_KEBAB__/auth'
|
|
2
|
+
import { createAuthClient } from 'better-auth/react'
|
|
3
|
+
import { inferAdditionalFields } from 'better-auth/client/plugins'
|
|
4
|
+
|
|
5
|
+
export function createAuthClientService() {
|
|
6
|
+
return createAuthClient({
|
|
7
|
+
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
|
8
|
+
plugins: [inferAdditionalFields<typeof auth>()],
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type AuthClient = ReturnType<typeof createAuthClientService>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createORPCClient } from '@orpc/client'
|
|
2
|
+
import { RPCLink } from '@orpc/client/fetch'
|
|
3
|
+
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
|
4
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
5
|
+
import type { RPCClient, ORPCUtils } from './types'
|
|
6
|
+
|
|
7
|
+
export function createQueryClient(): QueryClient {
|
|
8
|
+
return new QueryClient()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createRPCLink() {
|
|
12
|
+
return new RPCLink({
|
|
13
|
+
url: `${import.meta.env.VITE_API_URL || 'http://localhost:3000'}/rpc`,
|
|
14
|
+
fetch(url, options) {
|
|
15
|
+
return fetch(url, {
|
|
16
|
+
...options,
|
|
17
|
+
credentials: 'include',
|
|
18
|
+
})
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createRPCClient(link: ReturnType<typeof createRPCLink>): RPCClient {
|
|
24
|
+
return createORPCClient(link)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createORPCUtils(client: RPCClient): ORPCUtils {
|
|
28
|
+
return createTanstackQueryUtils(client)
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import type { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
|
3
|
+
import type { AuthClient } from './auth-client'
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
export type RPCClient = Record<string, any>
|
|
7
|
+
export type ORPCUtils = ReturnType<typeof createTanstackQueryUtils<RPCClient>>
|
|
8
|
+
|
|
9
|
+
export interface Services {
|
|
10
|
+
queryClient: QueryClient
|
|
11
|
+
rpcClient: RPCClient
|
|
12
|
+
orpc: ORPCUtils
|
|
13
|
+
authClient: AuthClient
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: __APP_NAME_KEBAB__
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
postgres:
|
|
5
|
+
image: postgres
|
|
6
|
+
container_name: __APP_NAME_KEBAB__-postgres
|
|
7
|
+
environment:
|
|
8
|
+
POSTGRES_DB: __APP_NAME_KEBAB__
|
|
9
|
+
POSTGRES_USER: postgres
|
|
10
|
+
POSTGRES_PASSWORD: password
|
|
11
|
+
ports:
|
|
12
|
+
- '5432:5432'
|
|
13
|
+
volumes:
|
|
14
|
+
- __APP_NAME_KEBAB___postgres_data:/var/lib/postgresql/data
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
|
17
|
+
interval: 10s
|
|
18
|
+
timeout: 5s
|
|
19
|
+
retries: 5
|
|
20
|
+
restart: unless-stopped
|
|
21
|
+
|
|
22
|
+
volumes:
|
|
23
|
+
__APP_NAME_KEBAB___postgres_data:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
import prettierConfig from 'eslint-config-prettier'
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
ignores: ['**/node_modules', '**/dist', '**/.turbo', '**/routeTree.gen.ts'],
|
|
8
|
+
},
|
|
9
|
+
js.configs.recommended,
|
|
10
|
+
...tseslint.configs.recommended,
|
|
11
|
+
{
|
|
12
|
+
rules: {
|
|
13
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
14
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
prettierConfig,
|
|
18
|
+
]
|
|
@@ -9,11 +9,41 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "turbo dev",
|
|
11
11
|
"build": "turbo build",
|
|
12
|
-
"check-types": "turbo check-types"
|
|
12
|
+
"check-types": "turbo check-types",
|
|
13
|
+
"lint": "eslint apps packages",
|
|
14
|
+
"lint:fix": "eslint apps packages --fix",
|
|
15
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
|
16
|
+
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
|
17
|
+
"db:generate": "turbo -F @__APP_NAME_KEBAB__/db db:generate",
|
|
18
|
+
"db:migrate": "turbo -F @__APP_NAME_KEBAB__/db db:migrate",
|
|
19
|
+
"db:studio": "turbo -F @__APP_NAME_KEBAB__/db db:studio",
|
|
20
|
+
"prepare": "husky"
|
|
21
|
+
},
|
|
22
|
+
"lint-staged": {
|
|
23
|
+
"*.{ts,tsx,js,jsx}": [
|
|
24
|
+
"eslint --max-warnings=0",
|
|
25
|
+
"prettier --write"
|
|
26
|
+
],
|
|
27
|
+
"*.{json,md,yml,yaml}": [
|
|
28
|
+
"prettier --write"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@kuckit/sdk": "^1.0.0",
|
|
33
|
+
"awilix": "^12.0.5",
|
|
34
|
+
"dotenv": "^17.2.2",
|
|
35
|
+
"zod": "^4.1.11"
|
|
13
36
|
},
|
|
14
37
|
"devDependencies": {
|
|
38
|
+
"@eslint/js": "^9.17.0",
|
|
39
|
+
"eslint": "^9.17.0",
|
|
40
|
+
"eslint-config-prettier": "^10.0.1",
|
|
41
|
+
"husky": "^9.1.7",
|
|
42
|
+
"lint-staged": "^16.0.0",
|
|
43
|
+
"prettier": "^3.4.2",
|
|
15
44
|
"turbo": "^2.5.4",
|
|
16
|
-
"typescript": "^5.8.2"
|
|
45
|
+
"typescript": "^5.8.2",
|
|
46
|
+
"typescript-eslint": "^8.18.2"
|
|
17
47
|
},
|
|
18
48
|
"packageManager": "bun@1.2.21"
|
|
19
49
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# AGENTS.md - API Package
|
|
2
|
+
|
|
3
|
+
> See root [AGENTS.md](../../AGENTS.md) for project overview
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Shared API context, types, and procedure definitions for oRPC.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
| ------------ | -------------------------------- |
|
|
13
|
+
| `context.ts` | Request context type definitions |
|
|
14
|
+
| `index.ts` | Public exports |
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { type Context, protectedProcedure } from '@__APP_NAME_KEBAB__/api'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Context Structure
|
|
23
|
+
|
|
24
|
+
The API context provides:
|
|
25
|
+
|
|
26
|
+
- `container` - Scoped DI container for the request
|
|
27
|
+
- `user` - Authenticated user (when using `protectedProcedure`)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@__APP_NAME_KEBAB__/api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./*": {
|
|
14
|
+
"types": "./src/*.ts",
|
|
15
|
+
"default": "./src/*.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/express": "^5.0.1"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"typescript": "^5"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@orpc/server": "^1.10.0",
|
|
26
|
+
"@orpc/client": "^1.10.0",
|
|
27
|
+
"@orpc/zod": "^1.10.0",
|
|
28
|
+
"better-auth": "^1.3.28",
|
|
29
|
+
"dotenv": "^17.2.2",
|
|
30
|
+
"zod": "^4.1.11",
|
|
31
|
+
"awilix": "^12.0.5",
|
|
32
|
+
"@__APP_NAME_KEBAB__/auth": "workspace:*",
|
|
33
|
+
"@__APP_NAME_KEBAB__/db": "workspace:*"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Request } from 'express'
|
|
2
|
+
import { fromNodeHeaders } from 'better-auth/node'
|
|
3
|
+
import { auth } from '@__APP_NAME_KEBAB__/auth'
|
|
4
|
+
import { asValue, type AwilixContainer } from 'awilix'
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
8
|
+
namespace Express {
|
|
9
|
+
interface Request {
|
|
10
|
+
scope?: AwilixContainer
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CreateContextOptions {
|
|
16
|
+
req: Request
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create per-request context with DI container
|
|
21
|
+
*/
|
|
22
|
+
export async function createContext(opts: CreateContextOptions) {
|
|
23
|
+
let session
|
|
24
|
+
try {
|
|
25
|
+
session = await auth.api.getSession({
|
|
26
|
+
headers: fromNodeHeaders(opts.req.headers),
|
|
27
|
+
})
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('[Auth] Failed to get session:', error)
|
|
30
|
+
session = null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const requestId = crypto.randomUUID()
|
|
34
|
+
|
|
35
|
+
if (opts.req.scope) {
|
|
36
|
+
opts.req.scope.register({
|
|
37
|
+
session: asValue(session),
|
|
38
|
+
requestId: asValue(requestId),
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
session,
|
|
44
|
+
di: opts.req.scope!,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type Context = Awaited<ReturnType<typeof createContext>>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ORPCError, os } from '@orpc/server'
|
|
2
|
+
import type { Context } from './context'
|
|
3
|
+
|
|
4
|
+
export const o = os.$context<Context>()
|
|
5
|
+
|
|
6
|
+
export const publicProcedure = o
|
|
7
|
+
|
|
8
|
+
const requireAuth = o.middleware(async ({ context, next }) => {
|
|
9
|
+
if (!context.session?.user) {
|
|
10
|
+
throw new ORPCError('UNAUTHORIZED')
|
|
11
|
+
}
|
|
12
|
+
return next({
|
|
13
|
+
context: {
|
|
14
|
+
session: context.session,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const protectedProcedure = publicProcedure.use(requireAuth)
|
|
20
|
+
|
|
21
|
+
export { createContext } from './context'
|
|
22
|
+
export type { Context } from './context'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# AGENTS.md - Auth Package
|
|
2
|
+
|
|
3
|
+
> See root [AGENTS.md](../../AGENTS.md) for project overview
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Better-Auth configuration shared between server and web apps.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
| ---------- | -------------------------------------- |
|
|
13
|
+
| `index.ts` | Auth configuration and client creation |
|
|
14
|
+
|
|
15
|
+
## Server Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { auth } from '@__APP_NAME_KEBAB__/auth'
|
|
19
|
+
|
|
20
|
+
// In Express
|
|
21
|
+
app.all('/api/auth/*', auth.handler)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Client Usage
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { authClient } from '@__APP_NAME_KEBAB__/auth'
|
|
28
|
+
|
|
29
|
+
// Sign in
|
|
30
|
+
await authClient.signIn.email({ email, password })
|
|
31
|
+
|
|
32
|
+
// Sign out
|
|
33
|
+
await authClient.signOut()
|
|
34
|
+
|
|
35
|
+
// Get session
|
|
36
|
+
const session = await authClient.getSession()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
Auth requires these environment variables:
|
|
42
|
+
|
|
43
|
+
- `BETTER_AUTH_SECRET` - Session encryption key
|
|
44
|
+
- `BETTER_AUTH_URL` - Callback URL for OAuth
|
|
45
|
+
- `DATABASE_URL` - For session storage
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@__APP_NAME_KEBAB__/auth",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./*": {
|
|
14
|
+
"types": "./src/*.ts",
|
|
15
|
+
"default": "./src/*.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"typescript": "^5"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"better-auth": "^1.3.28",
|
|
23
|
+
"dotenv": "^17.2.2",
|
|
24
|
+
"zod": "^4.1.11",
|
|
25
|
+
"@__APP_NAME_KEBAB__/db": "workspace:*"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { betterAuth, type BetterAuthOptions } from 'better-auth'
|
|
2
|
+
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
3
|
+
import { db } from '@__APP_NAME_KEBAB__/db'
|
|
4
|
+
import * as schema from '@__APP_NAME_KEBAB__/db/schema/auth'
|
|
5
|
+
|
|
6
|
+
export const auth = betterAuth<BetterAuthOptions>({
|
|
7
|
+
database: drizzleAdapter(db, {
|
|
8
|
+
provider: 'pg',
|
|
9
|
+
schema: schema,
|
|
10
|
+
}),
|
|
11
|
+
trustedOrigins: [process.env.CORS_ORIGIN || ''],
|
|
12
|
+
emailAndPassword: {
|
|
13
|
+
enabled: true,
|
|
14
|
+
},
|
|
15
|
+
advanced: {
|
|
16
|
+
defaultCookieAttributes: {
|
|
17
|
+
sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
|
|
18
|
+
secure: process.env.NODE_ENV === 'production',
|
|
19
|
+
httpOnly: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# AGENTS.md - Database Package
|
|
2
|
+
|
|
3
|
+
> See root [AGENTS.md](../../AGENTS.md) for project overview
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Drizzle ORM configuration, database connection, migrations, and shared schema.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
| ------------------- | ------------------------- |
|
|
13
|
+
| `drizzle.config.ts` | Drizzle Kit configuration |
|
|
14
|
+
| `src/connection.ts` | Database connection pool |
|
|
15
|
+
| `src/schema/` | Shared schema definitions |
|
|
16
|
+
| `src/migrations/` | SQL migration files |
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Generate migration from schema changes
|
|
22
|
+
bun run db:generate
|
|
23
|
+
|
|
24
|
+
# Apply pending migrations
|
|
25
|
+
bun run db:migrate
|
|
26
|
+
|
|
27
|
+
# Open Drizzle Studio (database GUI)
|
|
28
|
+
bun run db:studio
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Adding a New Table
|
|
32
|
+
|
|
33
|
+
1. Create schema file in `src/schema/` or in your module's `adapters/`:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
|
37
|
+
|
|
38
|
+
export const myTable = pgTable('my_table', {
|
|
39
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
40
|
+
name: text('name').notNull(),
|
|
41
|
+
createdAt: timestamp('created_at').defaultNow(),
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. Export from `src/index.ts` if shared
|
|
46
|
+
|
|
47
|
+
3. Generate and run migration:
|
|
48
|
+
```bash
|
|
49
|
+
bun run db:generate
|
|
50
|
+
bun run db:migrate
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Connection
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { db } from '@__APP_NAME_KEBAB__/db'
|
|
57
|
+
|
|
58
|
+
const users = await db.select().from(usersTable)
|
|
59
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit'
|
|
2
|
+
import dotenv from 'dotenv'
|
|
3
|
+
import { dirname, resolve } from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { buildDatabaseUrl } from './src/connection'
|
|
6
|
+
|
|
7
|
+
const currentFilePath = fileURLToPath(import.meta.url)
|
|
8
|
+
const currentDirPath = dirname(currentFilePath)
|
|
9
|
+
|
|
10
|
+
dotenv.config({
|
|
11
|
+
path: resolve(currentDirPath, '../../apps/server/.env'),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
schema: [resolve(currentDirPath, './src/schema')],
|
|
16
|
+
out: resolve(currentDirPath, './src/migrations'),
|
|
17
|
+
dialect: 'postgresql',
|
|
18
|
+
dbCredentials: { url: buildDatabaseUrl() },
|
|
19
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@__APP_NAME_KEBAB__/db",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./*": {
|
|
14
|
+
"types": "./src/*.ts",
|
|
15
|
+
"default": "./src/*.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"db:generate": "drizzle-kit generate",
|
|
20
|
+
"db:migrate": "drizzle-kit migrate",
|
|
21
|
+
"db:studio": "drizzle-kit studio"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"drizzle-kit": "^0.31.2",
|
|
25
|
+
"@types/pg": "^8.11.11"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"typescript": "^5"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"drizzle-orm": "^0.44.2",
|
|
32
|
+
"pg": "^8.14.1",
|
|
33
|
+
"dotenv": "^17.2.2",
|
|
34
|
+
"zod": "^4.1.11"
|
|
35
|
+
}
|
|
36
|
+
}
|