@kevinmarrec/create-app 0.7.0 → 0.8.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/README.md +1 -1
- package/dist/index.js +1 -3
- package/package.json +21 -17
- package/template/.github/workflows/ci.yml +3 -3
- package/template/.scripts/dev.ts +8 -0
- package/template/.vscode/settings.json +1 -1
- package/template/client/.env +1 -0
- package/template/{frontend → client}/package.json +11 -9
- package/template/{frontend → client}/src/App.vue +9 -2
- package/template/client/src/composables/auth.ts +39 -0
- package/template/client/src/composables/content.ts +12 -0
- package/template/{frontend → client}/src/composables/index.ts +1 -0
- package/template/{frontend → client}/src/lib/orpc.ts +2 -2
- package/template/compose.yaml +5 -5
- package/template/knip.config.ts +11 -6
- package/template/package.json +10 -10
- package/template/server/.env.development +4 -0
- package/template/{backend → server}/package.json +7 -7
- package/template/{backend → server}/src/auth/index.ts +2 -0
- package/template/{backend → server}/src/database/index.ts +1 -1
- package/template/{backend → server}/src/env.d.ts +2 -2
- package/template/{backend → server}/src/main.ts +20 -16
- package/template/server/src/orpc/context.ts +10 -0
- package/template/server/src/orpc/handler.ts +34 -0
- package/template/server/src/orpc/index.ts +18 -0
- package/template/server/src/orpc/middlewares/auth.ts +23 -0
- package/template/server/src/orpc/router/index.ts +19 -0
- package/template/server/src/utils/cors.ts +26 -0
- package/template/tsconfig.json +2 -2
- package/template/backend/.env.development +0 -4
- package/template/backend/src/orpc/index.ts +0 -65
- package/template/backend/src/orpc/middlewares/auth.ts +0 -33
- package/template/backend/src/orpc/router/auth.ts +0 -54
- package/template/backend/src/orpc/router/index.ts +0 -9
- package/template/frontend/.env.development +0 -1
- package/template/frontend/src/composables/auth.ts +0 -31
- package/template/scripts/dev.ts +0 -8
- /package/template/{gitignore → .gitignore} +0 -0
- /package/template/{npmrc → .npmrc} +0 -0
- /package/template/{frontend → client}/index.html +0 -0
- /package/template/{frontend → client}/public/favicon.svg +0 -0
- /package/template/{frontend → client}/public/robots.txt +0 -0
- /package/template/{frontend → client}/src/components/.gitkeep +0 -0
- /package/template/{frontend → client}/src/env.d.ts +0 -0
- /package/template/{frontend → client}/src/locales/en.yml +0 -0
- /package/template/{frontend → client}/src/locales/fr.yml +0 -0
- /package/template/{frontend → client}/src/main.ts +0 -0
- /package/template/{frontend → client}/tsconfig.json +0 -0
- /package/template/{frontend → client}/uno.config.ts +0 -0
- /package/template/{frontend → client}/vite.config.ts +0 -0
- /package/template/{frontend → client}/wrangler.json +0 -0
- /package/template/{backend → server}/src/database/drizzle/config.ts +0 -0
- /package/template/{backend → server}/src/database/migrations/0000_init.sql +0 -0
- /package/template/{backend → server}/src/database/migrations/meta/0000_snapshot.json +0 -0
- /package/template/{backend → server}/src/database/migrations/meta/_journal.json +0 -0
- /package/template/{backend → server}/src/database/schema/accounts.ts +0 -0
- /package/template/{backend → server}/src/database/schema/index.ts +0 -0
- /package/template/{backend → server}/src/database/schema/sessions.ts +0 -0
- /package/template/{backend → server}/src/database/schema/users.ts +0 -0
- /package/template/{backend → server}/src/database/schema/verifications.ts +0 -0
- /package/template/{backend → server}/src/orpc/middlewares/index.ts +0 -0
- /package/template/{backend → server}/src/utils/logger.ts +0 -0
- /package/template/{backend → server}/tsconfig.json +0 -0
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { x } from "tinyexec";
|
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "0.
|
|
11
|
+
var version = "0.8.0";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/utils/fs.ts
|
|
@@ -35,8 +35,6 @@ var fs_default = {
|
|
|
35
35
|
async function scaffold(root) {
|
|
36
36
|
await fs_default.exists(root) ? await fs_default.empty(root) : await fs_default.mkdir(root);
|
|
37
37
|
await fs_default.cp(join(import.meta.dirname, "../template"), root, { recursive: true });
|
|
38
|
-
await fs_default.rename(join(root, "gitignore"), join(root, ".gitignore"));
|
|
39
|
-
await fs_default.rename(join(root, "npmrc"), join(root, ".npmrc"));
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevinmarrec/create-app",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"packageManager": "bun@1.3.1",
|
|
6
6
|
"description": "CLI that scaffolds an opinionated Bun & Vue fullstack application.",
|
|
7
7
|
"author": "Kevin Marrec <kevin@marrec.io>",
|
|
@@ -18,19 +18,21 @@
|
|
|
18
18
|
],
|
|
19
19
|
"workspaces": [
|
|
20
20
|
"template",
|
|
21
|
-
"template/
|
|
22
|
-
"template/
|
|
21
|
+
"template/client",
|
|
22
|
+
"template/server"
|
|
23
23
|
],
|
|
24
24
|
"bin": {
|
|
25
25
|
"create-app": "dist/index.js"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|
|
29
|
-
"template"
|
|
29
|
+
"template",
|
|
30
|
+
"template/.gitignore",
|
|
31
|
+
"template/.npmrc"
|
|
30
32
|
],
|
|
31
33
|
"scripts": {
|
|
32
|
-
"build": "tsdown",
|
|
33
|
-
"check": "bun run check:
|
|
34
|
+
"build": "tsdown --no-fixed-extension",
|
|
35
|
+
"check": "bun run check:eslint && bun run check:stylelint && bun run check:unused && bun run check:types",
|
|
34
36
|
"check:eslint": "eslint . --cache",
|
|
35
37
|
"check:stylelint": "stylelint '**/*.{css,scss,vue}' --ignorePath .gitignore --cache",
|
|
36
38
|
"check:types": "vue-tsc --noEmit",
|
|
@@ -47,22 +49,24 @@
|
|
|
47
49
|
"@clack/prompts": "^0.11.0",
|
|
48
50
|
"ansis": "^4.2.0",
|
|
49
51
|
"pathe": "^2.0.3",
|
|
50
|
-
"tinyexec": "^1.0.
|
|
52
|
+
"tinyexec": "^1.0.2"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
55
|
"@faker-js/faker": "^10.1.0",
|
|
54
|
-
"@kevinmarrec/eslint-config": "^1.5.
|
|
55
|
-
"@kevinmarrec/stylelint-config": "^1.
|
|
56
|
-
"@kevinmarrec/tsconfig": "^1.
|
|
57
|
-
"@types/bun": "^1.3.
|
|
58
|
-
"@vitest/coverage-v8": "^4.0.
|
|
56
|
+
"@kevinmarrec/eslint-config": "^1.5.4",
|
|
57
|
+
"@kevinmarrec/stylelint-config": "^1.5.4",
|
|
58
|
+
"@kevinmarrec/tsconfig": "^1.5.4",
|
|
59
|
+
"@types/bun": "^1.3.1",
|
|
60
|
+
"@vitest/coverage-v8": "^4.0.7",
|
|
59
61
|
"bumpp": "^10.3.1",
|
|
60
|
-
"eslint": "^9.
|
|
61
|
-
"
|
|
62
|
+
"eslint": "^9.39.1",
|
|
63
|
+
"filesize": "^11.0.13",
|
|
64
|
+
"knip": "^5.67.1",
|
|
62
65
|
"stylelint": "^16.25.0",
|
|
63
|
-
"
|
|
66
|
+
"tinyglobby": "^0.2.15",
|
|
67
|
+
"tsdown": "^0.16.0",
|
|
64
68
|
"typescript": "^5.9.3",
|
|
65
|
-
"vitest": "^4.0.
|
|
66
|
-
"vue-tsc": "^3.1.
|
|
69
|
+
"vitest": "^4.0.7",
|
|
70
|
+
"vue-tsc": "^3.1.3"
|
|
67
71
|
}
|
|
68
72
|
}
|
|
@@ -16,11 +16,11 @@ jobs:
|
|
|
16
16
|
- name: Setup Bun
|
|
17
17
|
uses: kevinmarrec/workflows/setup-bun@v1
|
|
18
18
|
|
|
19
|
+
- name: Lint
|
|
20
|
+
run: bun run lint
|
|
21
|
+
|
|
19
22
|
- name: Check unused files, dependencies, and exports
|
|
20
23
|
run: bun run check:unused
|
|
21
24
|
|
|
22
|
-
- name: Lint
|
|
23
|
-
run: bun run check:eslint && bun run check:stylelint
|
|
24
|
-
|
|
25
25
|
- name: Type check
|
|
26
26
|
run: bun run check:types
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import concurrently, { type ConcurrentlyCommandInput } from 'concurrently'
|
|
2
|
+
|
|
3
|
+
const commandInputs: ConcurrentlyCommandInput[] = [
|
|
4
|
+
{ name: 'server', command: `bun --cwd server dev | pino-pretty`, prefixColor: 'blue' },
|
|
5
|
+
{ name: 'client', command: `bun --cwd client dev`, prefixColor: 'green' },
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
concurrently(commandInputs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VITE_API_URL=http://localhost:4000
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "client",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
@@ -9,17 +9,19 @@
|
|
|
9
9
|
"preview": "vite preview --open"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@kevinmarrec/vue-i18n": "^1.1.
|
|
13
|
-
"@orpc/client": "^1.
|
|
14
|
-
"@orpc/tanstack-query": "1.
|
|
15
|
-
"@tanstack/query-core": "^5.90.
|
|
16
|
-
"@tanstack/vue-query": "^5.90.
|
|
12
|
+
"@kevinmarrec/vue-i18n": "^1.1.3",
|
|
13
|
+
"@orpc/client": "^1.10.4",
|
|
14
|
+
"@orpc/tanstack-query": "1.10.3",
|
|
15
|
+
"@tanstack/query-core": "^5.90.7",
|
|
16
|
+
"@tanstack/vue-query": "^5.90.7",
|
|
17
17
|
"@unhead/vue": "^2.0.19",
|
|
18
|
+
"@vueuse/core": "^14.0.0",
|
|
19
|
+
"better-auth": "^1.3.34",
|
|
18
20
|
"unocss": "^66.5.4",
|
|
19
21
|
"vue": "^3.5.22"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
22
|
-
"@kevinmarrec/unocss-config": "^1.
|
|
24
|
+
"@kevinmarrec/unocss-config": "^1.5.4",
|
|
23
25
|
"@kevinmarrec/vite-plugin-dark-mode": "^1.1.2",
|
|
24
26
|
"@modyfi/vite-plugin-yaml": "^1.1.1",
|
|
25
27
|
"@unhead/addons": "^2.0.19",
|
|
@@ -27,9 +29,9 @@
|
|
|
27
29
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
28
30
|
"beasties": "^0.3.5",
|
|
29
31
|
"rollup-plugin-visualizer": "^6.0.5",
|
|
30
|
-
"vite": "^7.
|
|
32
|
+
"vite": "^7.2.0",
|
|
31
33
|
"vite-plugin-vue-devtools": "^8.0.3",
|
|
32
|
-
"vite-ssg": "^28.2.
|
|
34
|
+
"vite-ssg": "^28.2.2",
|
|
33
35
|
"vite-tsconfig-paths": "^5.1.4"
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useAuth, useHead, useI18n } from '@
|
|
2
|
+
import { useAuth, useContent, useHead, useI18n } from '@client/composables'
|
|
3
3
|
import { ref } from 'vue'
|
|
4
4
|
|
|
5
5
|
const { t } = useI18n()
|
|
@@ -12,7 +12,8 @@ useHead({
|
|
|
12
12
|
}],
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
const { user,
|
|
15
|
+
const { user, signIn, signUp, signOut } = useAuth()
|
|
16
|
+
const { publicContent, privateContent } = useContent()
|
|
16
17
|
|
|
17
18
|
const email = ref('')
|
|
18
19
|
const password = ref('')
|
|
@@ -99,6 +100,12 @@ const password = ref('')
|
|
|
99
100
|
<p class="text-gray-600 mt-2 dark:text-gray-400">
|
|
100
101
|
{{ user.email }}
|
|
101
102
|
</p>
|
|
103
|
+
<p class="text-gray-600 mt-2 dark:text-gray-400">
|
|
104
|
+
{{ publicContent }}
|
|
105
|
+
</p>
|
|
106
|
+
<p class="text-gray-600 mt-2 dark:text-gray-400">
|
|
107
|
+
{{ privateContent }}
|
|
108
|
+
</p>
|
|
102
109
|
</div>
|
|
103
110
|
|
|
104
111
|
<button
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/vue-query'
|
|
2
|
+
import { createAuthClient } from 'better-auth/vue'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
|
|
5
|
+
const authClient = createAuthClient({
|
|
6
|
+
baseURL: `${import.meta.env.VITE_API_URL}/auth`,
|
|
7
|
+
fetchOptions: {
|
|
8
|
+
credentials: 'include',
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const session = authClient.useSession()
|
|
13
|
+
|
|
14
|
+
function createAuthMutation<T extends (...args: any[]) => any>(fn: T) {
|
|
15
|
+
return useMutation({
|
|
16
|
+
mutationFn: async (input: Parameters<T>[0]) => {
|
|
17
|
+
return fn(input, {
|
|
18
|
+
onError: ({ error }: any) => {
|
|
19
|
+
throw error
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useAuth() {
|
|
27
|
+
const signIn = createAuthMutation(authClient.signIn.email)
|
|
28
|
+
const signUp = createAuthMutation(authClient.signUp.email)
|
|
29
|
+
const signOut = createAuthMutation(authClient.signOut)
|
|
30
|
+
|
|
31
|
+
const user = computed(() => session.value.data?.user)
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
signIn,
|
|
35
|
+
signUp,
|
|
36
|
+
signOut,
|
|
37
|
+
user,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { orpc } from '@client/lib/orpc'
|
|
2
|
+
import { useQuery } from '@tanstack/vue-query'
|
|
3
|
+
|
|
4
|
+
export function useContent() {
|
|
5
|
+
const publicContent = useQuery(orpc.public.queryOptions({})).data
|
|
6
|
+
const privateContent = useQuery(orpc.private.queryOptions({})).data
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
publicContent,
|
|
10
|
+
privateContent,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { Router, RouterClient } from '@backend/orpc/router'
|
|
2
1
|
import { createORPCClient } from '@orpc/client'
|
|
3
2
|
import { RPCLink } from '@orpc/client/fetch'
|
|
4
3
|
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
|
4
|
+
import type { Router, RouterClient } from '@server/orpc/router'
|
|
5
5
|
|
|
6
6
|
const link = new RPCLink({
|
|
7
|
-
url: import.meta.env.VITE_API_URL
|
|
7
|
+
url: `${import.meta.env.VITE_API_URL}/rpc`,
|
|
8
8
|
fetch: (request, init) =>
|
|
9
9
|
globalThis.fetch(request, {
|
|
10
10
|
...init,
|
package/template/compose.yaml
CHANGED
|
@@ -5,18 +5,18 @@ x-common: &common
|
|
|
5
5
|
user: 1000:1000
|
|
6
6
|
|
|
7
7
|
services:
|
|
8
|
-
|
|
8
|
+
server:
|
|
9
9
|
<<: *common
|
|
10
10
|
image: oven/bun:1-alpine
|
|
11
|
-
command: [bun, --cwd,
|
|
11
|
+
command: [bun, --cwd, server, dev]
|
|
12
12
|
ports:
|
|
13
13
|
- 4000:4000
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
client:
|
|
16
16
|
<<: *common
|
|
17
17
|
depends_on:
|
|
18
|
-
-
|
|
18
|
+
- server
|
|
19
19
|
image: imbios/bun-node:22-alpine
|
|
20
|
-
command: [bun, --cwd,
|
|
20
|
+
command: [bun, --cwd, client, dev, --host, 0.0.0.0]
|
|
21
21
|
ports:
|
|
22
22
|
- 5173:5173
|
package/template/knip.config.ts
CHANGED
|
@@ -10,15 +10,20 @@ export default {
|
|
|
10
10
|
'.': {
|
|
11
11
|
entry: ['*.config.ts'],
|
|
12
12
|
},
|
|
13
|
-
'
|
|
13
|
+
'client': {
|
|
14
|
+
entry: ['src/main.ts'],
|
|
15
|
+
ignoreDependencies: [
|
|
16
|
+
'@vueuse/core',
|
|
17
|
+
'uno.css',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
'server': {
|
|
14
21
|
drizzle: {
|
|
15
22
|
config: 'src/database/drizzle/config.ts',
|
|
16
23
|
},
|
|
17
|
-
ignoreDependencies: [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
entry: ['src/main.ts'],
|
|
21
|
-
ignoreDependencies: ['uno.css'],
|
|
24
|
+
ignoreDependencies: [
|
|
25
|
+
'pino-pretty',
|
|
26
|
+
],
|
|
22
27
|
},
|
|
23
28
|
},
|
|
24
29
|
} satisfies KnipConfig
|
package/template/package.json
CHANGED
|
@@ -7,29 +7,29 @@
|
|
|
7
7
|
"node": "lts/*"
|
|
8
8
|
},
|
|
9
9
|
"workspaces": [
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"client",
|
|
11
|
+
"server"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"check": "bun run check:
|
|
14
|
+
"check": "bun run check:eslint && bun run check:stylelint && bun run check:unused && bun run check:types",
|
|
15
15
|
"check:eslint": "eslint . --cache",
|
|
16
16
|
"check:stylelint": "stylelint '**/*.{css,scss,vue}' --ignorePath .gitignore --cache",
|
|
17
17
|
"check:types": "vue-tsc --noEmit",
|
|
18
18
|
"check:unused": "knip -n",
|
|
19
|
-
"dev": "bun scripts/dev.ts",
|
|
19
|
+
"dev": "bun .scripts/dev.ts",
|
|
20
20
|
"lint": "bun run check:eslint && bun run check:stylelint",
|
|
21
21
|
"lint:inspect": "bunx @eslint/config-inspector",
|
|
22
22
|
"update": "bunx taze -I -rwi"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@kevinmarrec/eslint-config": "^1.5.
|
|
26
|
-
"@kevinmarrec/stylelint-config": "^1.
|
|
27
|
-
"@kevinmarrec/tsconfig": "^1.
|
|
25
|
+
"@kevinmarrec/eslint-config": "^1.5.4",
|
|
26
|
+
"@kevinmarrec/stylelint-config": "^1.5.4",
|
|
27
|
+
"@kevinmarrec/tsconfig": "^1.5.4",
|
|
28
28
|
"concurrently": "^9.2.1",
|
|
29
|
-
"eslint": "^9.
|
|
30
|
-
"knip": "^5.
|
|
29
|
+
"eslint": "^9.39.1",
|
|
30
|
+
"knip": "^5.67.1",
|
|
31
31
|
"stylelint": "^16.25.0",
|
|
32
32
|
"typescript": "~5.9.3",
|
|
33
|
-
"vue-tsc": "^3.1.
|
|
33
|
+
"vue-tsc": "^3.1.3"
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "server",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
"db:migrate": "bun --bun run drizzle-kit migrate --config src/database/drizzle/config.ts"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@orpc/server": "^1.
|
|
13
|
-
"better-auth": "^1.3.
|
|
14
|
-
"drizzle-orm": "^0.44.
|
|
12
|
+
"@orpc/server": "^1.10.4",
|
|
13
|
+
"better-auth": "^1.3.34",
|
|
14
|
+
"drizzle-orm": "^0.44.7",
|
|
15
15
|
"pino": "^10.1.0",
|
|
16
16
|
"valibot": "^1.1.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@electric-sql/pglite": "^0.3.
|
|
20
|
-
"@types/bun": "^1.3.
|
|
21
|
-
"drizzle-kit": "^0.31.
|
|
19
|
+
"@electric-sql/pglite": "^0.3.13",
|
|
20
|
+
"@types/bun": "^1.3.1",
|
|
21
|
+
"drizzle-kit": "^0.31.6",
|
|
22
22
|
"pino-pretty": "^13.1.2"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -4,6 +4,8 @@ import type { BaseLogger } from 'pino'
|
|
|
4
4
|
|
|
5
5
|
export function createBetterAuth({ db, logger }: { db: DB, logger: BaseLogger }) {
|
|
6
6
|
return betterAuth({
|
|
7
|
+
basePath: '/auth',
|
|
8
|
+
trustedOrigins: import.meta.env.ALLOWED_ORIGINS.split(','),
|
|
7
9
|
database: drizzleAdapter(db, {
|
|
8
10
|
provider: 'pg',
|
|
9
11
|
usePlural: true,
|
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
2
|
|
|
3
|
-
import { createBetterAuth } from '@
|
|
4
|
-
import { db } from '@
|
|
5
|
-
import { createRpcHandler } from '@
|
|
6
|
-
import { router } from '@
|
|
7
|
-
import {
|
|
3
|
+
import { createBetterAuth } from '@server/auth'
|
|
4
|
+
import { db } from '@server/database'
|
|
5
|
+
import { createRpcHandler } from '@server/orpc'
|
|
6
|
+
import { router } from '@server/orpc/router'
|
|
7
|
+
import { cors } from '@server/utils/cors'
|
|
8
|
+
import { logger } from '@server/utils/logger'
|
|
8
9
|
|
|
9
10
|
const auth = createBetterAuth({ db, logger })
|
|
10
11
|
const rpcHandler = createRpcHandler(router)
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const routes = {
|
|
14
|
+
'/auth/*': cors(async (req) => {
|
|
15
|
+
return await auth.handler(req)
|
|
16
|
+
}),
|
|
17
|
+
'/rpc/*': cors(async (req) => {
|
|
18
|
+
const { matched, response } = await rpcHandler.handle(req, {
|
|
17
19
|
prefix: '/rpc',
|
|
18
|
-
context: {
|
|
19
|
-
auth,
|
|
20
|
-
db,
|
|
21
|
-
logger,
|
|
22
|
-
},
|
|
20
|
+
context: { auth, db, logger },
|
|
23
21
|
})
|
|
24
22
|
|
|
25
23
|
if (matched)
|
|
26
24
|
return response
|
|
27
25
|
|
|
28
26
|
return new Response('Not found', { status: 404 })
|
|
29
|
-
},
|
|
27
|
+
}),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const server = Bun.serve({
|
|
31
|
+
hostname: import.meta.env.HOST ?? '0.0.0.0',
|
|
32
|
+
port: import.meta.env.PORT ?? 4000,
|
|
33
|
+
routes,
|
|
30
34
|
error(error) {
|
|
31
35
|
logger.error(error)
|
|
32
36
|
return new Response('Internal Server Error', { status: 500 })
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RequestHeadersPluginContext, ResponseHeadersPluginContext } from '@orpc/server/plugins'
|
|
2
|
+
import type { Auth } from '@server/auth'
|
|
3
|
+
import type { Database } from '@server/database'
|
|
4
|
+
import type { Logger } from '@server/utils/logger'
|
|
5
|
+
|
|
6
|
+
export interface Context extends RequestHeadersPluginContext, ResponseHeadersPluginContext {
|
|
7
|
+
auth: Auth
|
|
8
|
+
db: Database
|
|
9
|
+
logger: Logger
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { onError, ORPCError, type Router } from '@orpc/server'
|
|
2
|
+
import { RPCHandler } from '@orpc/server/fetch'
|
|
3
|
+
import {
|
|
4
|
+
RequestHeadersPlugin,
|
|
5
|
+
ResponseHeadersPlugin,
|
|
6
|
+
} from '@orpc/server/plugins'
|
|
7
|
+
import { APIError } from 'better-auth/api'
|
|
8
|
+
|
|
9
|
+
import type { Context } from './context'
|
|
10
|
+
|
|
11
|
+
export function createRpcHandler<T extends Context>(router: Router<any, T>) {
|
|
12
|
+
return new RPCHandler<T>(router, {
|
|
13
|
+
plugins: [
|
|
14
|
+
new RequestHeadersPlugin(),
|
|
15
|
+
new ResponseHeadersPlugin(),
|
|
16
|
+
],
|
|
17
|
+
clientInterceptors: [
|
|
18
|
+
onError((error, { context }) => {
|
|
19
|
+
if (error instanceof APIError) {
|
|
20
|
+
throw new ORPCError(error.body?.code ?? 'INTERNAL_SERVER_ERROR', {
|
|
21
|
+
status: error.statusCode,
|
|
22
|
+
message: error.body?.message,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (error instanceof ORPCError) {
|
|
27
|
+
throw error
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
context.logger.error(error)
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { os } from '@orpc/server'
|
|
2
|
+
import { authMiddleware } from '@server/orpc/middlewares'
|
|
3
|
+
|
|
4
|
+
import type { Context } from './context'
|
|
5
|
+
|
|
6
|
+
export { createRpcHandler } from './handler'
|
|
7
|
+
|
|
8
|
+
export type { Context }
|
|
9
|
+
|
|
10
|
+
export const pub = os
|
|
11
|
+
.$context<Context>()
|
|
12
|
+
.errors({
|
|
13
|
+
UNAUTHORIZED: { status: 401 },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/** @beta */
|
|
17
|
+
export const authed = pub
|
|
18
|
+
.use(authMiddleware)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ORPCError, os } from '@orpc/server'
|
|
2
|
+
import type { Context } from '@server/orpc'
|
|
3
|
+
|
|
4
|
+
export const authMiddleware = os
|
|
5
|
+
.$context<Context>()
|
|
6
|
+
.middleware(async ({ context, next }) => {
|
|
7
|
+
const { response: session, headers } = await context.auth.api.getSession({
|
|
8
|
+
headers: context.reqHeaders,
|
|
9
|
+
returnHeaders: true,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
headers.forEach((v, k) => context.resHeaders?.append(k, v))
|
|
13
|
+
|
|
14
|
+
if (!session) {
|
|
15
|
+
throw new ORPCError('UNAUTHORIZED')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return next({
|
|
19
|
+
context: {
|
|
20
|
+
user: session.user,
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { authed, pub } from '@server/orpc'
|
|
2
|
+
import * as v from 'valibot'
|
|
3
|
+
|
|
4
|
+
export type { RouterClient } from '@orpc/server'
|
|
5
|
+
|
|
6
|
+
export const router = {
|
|
7
|
+
public: pub
|
|
8
|
+
.input(v.any())
|
|
9
|
+
.handler(async () => {
|
|
10
|
+
return 'public'
|
|
11
|
+
}),
|
|
12
|
+
private: authed
|
|
13
|
+
.input(v.any())
|
|
14
|
+
.handler(async () => {
|
|
15
|
+
return 'private'
|
|
16
|
+
}),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Router = typeof router
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function cors(handler: (req: Request) => Promise<Response>) {
|
|
2
|
+
const allowedOrigins = import.meta.env.ALLOWED_ORIGINS.split(',')
|
|
3
|
+
|
|
4
|
+
return async (req: Request) => {
|
|
5
|
+
const origin = req.headers.get('origin') ?? ''
|
|
6
|
+
|
|
7
|
+
if (!origin || !allowedOrigins.includes(origin)) {
|
|
8
|
+
return new Response('Origin not allowed', { status: 403 })
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const response = req.method === 'OPTIONS'
|
|
12
|
+
? new Response(undefined, { status: 204 })
|
|
13
|
+
: await handler(req)
|
|
14
|
+
|
|
15
|
+
if (req.method === 'OPTIONS') {
|
|
16
|
+
response.headers.append('Access-Control-Allow-Headers', 'Content-Type')
|
|
17
|
+
response.headers.append('Access-Control-Allow-Methods', 'GET, HEAD, PUT, POST, DELETE, PATCH')
|
|
18
|
+
response.headers.append('Access-Control-Max-Age', '7200') // 2 hours https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
response.headers.append('Access-Control-Allow-Credentials', 'true')
|
|
22
|
+
response.headers.append('Access-Control-Allow-Origin', origin)
|
|
23
|
+
|
|
24
|
+
return response
|
|
25
|
+
}
|
|
26
|
+
}
|
package/template/tsconfig.json
CHANGED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { Auth } from '@backend/auth'
|
|
2
|
-
import type { Database } from '@backend/database'
|
|
3
|
-
import { requiredAuthMiddleware } from '@backend/orpc/middlewares'
|
|
4
|
-
import type { Logger } from '@backend/utils/logger'
|
|
5
|
-
import { onError, ORPCError, os, type Router } from '@orpc/server'
|
|
6
|
-
import { RPCHandler } from '@orpc/server/fetch'
|
|
7
|
-
import {
|
|
8
|
-
CORSPlugin,
|
|
9
|
-
RequestHeadersPlugin,
|
|
10
|
-
type RequestHeadersPluginContext,
|
|
11
|
-
ResponseHeadersPlugin,
|
|
12
|
-
type ResponseHeadersPluginContext,
|
|
13
|
-
} from '@orpc/server/plugins'
|
|
14
|
-
import { APIError } from 'better-auth/api'
|
|
15
|
-
|
|
16
|
-
/* Context */
|
|
17
|
-
|
|
18
|
-
export interface Context extends RequestHeadersPluginContext, ResponseHeadersPluginContext {
|
|
19
|
-
auth: Auth
|
|
20
|
-
db: Database
|
|
21
|
-
logger: Logger
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/* RPC Handler */
|
|
25
|
-
|
|
26
|
-
export function createRpcHandler<T extends Context>(router: Router<any, T>) {
|
|
27
|
-
return new RPCHandler<T>(router, {
|
|
28
|
-
plugins: [
|
|
29
|
-
new CORSPlugin({
|
|
30
|
-
credentials: true,
|
|
31
|
-
maxAge: 7200, // 2 hours https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age
|
|
32
|
-
}),
|
|
33
|
-
new RequestHeadersPlugin(),
|
|
34
|
-
new ResponseHeadersPlugin(),
|
|
35
|
-
],
|
|
36
|
-
clientInterceptors: [
|
|
37
|
-
onError((error, { context }) => {
|
|
38
|
-
if (error instanceof APIError) {
|
|
39
|
-
throw new ORPCError(error.body?.code ?? 'INTERNAL_SERVER_ERROR', {
|
|
40
|
-
status: error.statusCode,
|
|
41
|
-
message: error.body?.message,
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (error instanceof ORPCError) {
|
|
46
|
-
throw error
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
context.logger.error(error)
|
|
50
|
-
}),
|
|
51
|
-
],
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* Builder */
|
|
56
|
-
|
|
57
|
-
export const pub = os
|
|
58
|
-
.$context<Context>()
|
|
59
|
-
.errors({
|
|
60
|
-
UNAUTHORIZED: { status: 401 },
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
/** @beta */
|
|
64
|
-
export const authed = pub
|
|
65
|
-
.use(requiredAuthMiddleware)
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Context } from '@backend/orpc'
|
|
2
|
-
import { ORPCError, os } from '@orpc/server'
|
|
3
|
-
|
|
4
|
-
export const authMiddleware = os
|
|
5
|
-
.$context<Context>()
|
|
6
|
-
.middleware(async ({ context, next }) => {
|
|
7
|
-
const { headers, response: session } = await context.auth.api.getSession({
|
|
8
|
-
headers: context.reqHeaders ?? new Headers(),
|
|
9
|
-
returnHeaders: true,
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
headers.forEach((v, k) => context.resHeaders?.append(k, v))
|
|
13
|
-
|
|
14
|
-
return next({
|
|
15
|
-
context: {
|
|
16
|
-
user: session ? session.user : null,
|
|
17
|
-
},
|
|
18
|
-
})
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
export const requiredAuthMiddleware = authMiddleware
|
|
22
|
-
.concat(async ({ context, next }) => {
|
|
23
|
-
if (!context.user) {
|
|
24
|
-
throw new ORPCError('UNAUTHORIZED')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return next({
|
|
28
|
-
context: {
|
|
29
|
-
user: context.user,
|
|
30
|
-
},
|
|
31
|
-
})
|
|
32
|
-
},
|
|
33
|
-
)
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { pub } from '@backend/orpc'
|
|
2
|
-
import { authMiddleware } from '@backend/orpc/middlewares'
|
|
3
|
-
import * as v from 'valibot'
|
|
4
|
-
|
|
5
|
-
export const getCurrentUser = pub
|
|
6
|
-
.use(authMiddleware)
|
|
7
|
-
.handler(async ({ context }) => context.user)
|
|
8
|
-
|
|
9
|
-
export const signUp = pub
|
|
10
|
-
.input(v.object({
|
|
11
|
-
name: v.string(),
|
|
12
|
-
email: v.pipe(v.string(), v.email()),
|
|
13
|
-
password: v.string(),
|
|
14
|
-
rememberMe: v.optional(v.boolean(), true),
|
|
15
|
-
}))
|
|
16
|
-
.handler(async ({ input, context: { resHeaders, auth } }) => {
|
|
17
|
-
const { headers, response } = await auth.api.signUpEmail({
|
|
18
|
-
body: input,
|
|
19
|
-
returnHeaders: true,
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
headers.forEach((v, k) => resHeaders?.append(k, v))
|
|
23
|
-
|
|
24
|
-
return response.user
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
export const signIn = pub
|
|
28
|
-
.input(v.object({
|
|
29
|
-
email: v.pipe(v.string(), v.email()),
|
|
30
|
-
password: v.string(),
|
|
31
|
-
rememberMe: v.optional(v.boolean(), true),
|
|
32
|
-
}))
|
|
33
|
-
.handler(async ({ input, context: { resHeaders, auth } }) => {
|
|
34
|
-
const { headers, response } = await auth.api.signInEmail({
|
|
35
|
-
body: input,
|
|
36
|
-
returnHeaders: true,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
headers.forEach((v, k) => resHeaders?.append(k, v))
|
|
40
|
-
|
|
41
|
-
return response.user
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
export const signOut = pub
|
|
45
|
-
.handler(async ({ context: { auth, reqHeaders, resHeaders } }) => {
|
|
46
|
-
const { headers, response } = await auth.api.signOut({
|
|
47
|
-
headers: reqHeaders ?? new Headers(),
|
|
48
|
-
returnHeaders: true,
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
headers.forEach((v, k) => resHeaders?.append(k, v))
|
|
52
|
-
|
|
53
|
-
return response
|
|
54
|
-
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VITE_API_URL=http://localhost:4000/rpc
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { orpc } from '@frontend/lib/orpc'
|
|
2
|
-
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
|
3
|
-
|
|
4
|
-
export function useAuth() {
|
|
5
|
-
const qc = useQueryClient()
|
|
6
|
-
|
|
7
|
-
const { data: user } = useQuery(orpc.auth.getCurrentUser.queryOptions({
|
|
8
|
-
retry: false,
|
|
9
|
-
}))
|
|
10
|
-
|
|
11
|
-
const signUp = useMutation(orpc.auth.signUp.mutationOptions({
|
|
12
|
-
onSuccess: () => qc.invalidateQueries({ queryKey: orpc.auth.getCurrentUser.queryKey() }),
|
|
13
|
-
}))
|
|
14
|
-
|
|
15
|
-
const signIn = useMutation(orpc.auth.signIn.mutationOptions({
|
|
16
|
-
onSuccess: () => qc.invalidateQueries({ queryKey: orpc.auth.getCurrentUser.queryKey() }),
|
|
17
|
-
}))
|
|
18
|
-
|
|
19
|
-
const signOut = useMutation(orpc.auth.signOut.mutationOptions({
|
|
20
|
-
onSuccess: () => {
|
|
21
|
-
qc.setQueryData<null>(orpc.auth.getCurrentUser.queryKey(), null)
|
|
22
|
-
},
|
|
23
|
-
}))
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
user,
|
|
27
|
-
signUp,
|
|
28
|
-
signIn,
|
|
29
|
-
signOut,
|
|
30
|
-
}
|
|
31
|
-
}
|
package/template/scripts/dev.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import concurrently, { type ConcurrentlyCommandInput } from 'concurrently'
|
|
2
|
-
|
|
3
|
-
const commandInputs: ConcurrentlyCommandInput[] = [
|
|
4
|
-
{ name: 'backend', command: `bun --cwd backend dev | pino-pretty`, prefixColor: 'blue' },
|
|
5
|
-
{ name: 'frontend', command: `bun --cwd frontend dev`, prefixColor: 'green' },
|
|
6
|
-
]
|
|
7
|
-
|
|
8
|
-
concurrently(commandInputs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|