@kevinmarrec/create-app 0.4.0 → 0.6.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/index.js +1 -1
- package/package.json +12 -11
- package/template/.vscode/extensions.json +2 -1
- package/template/.vscode/settings.json +4 -0
- package/template/backend/.env.development +3 -1
- package/template/backend/package.json +9 -8
- package/template/backend/src/auth/index.ts +23 -0
- package/template/backend/src/database/drizzle/config.ts +9 -0
- package/template/backend/src/database/index.ts +4 -4
- package/template/backend/src/database/migrations/0000_violet_enchantress.sql +49 -0
- package/template/backend/src/database/migrations/meta/0000_snapshot.json +292 -7
- package/template/backend/src/database/migrations/meta/_journal.json +2 -2
- package/template/backend/src/database/schema/accounts.ts +20 -0
- package/template/backend/src/database/schema/index.ts +3 -0
- package/template/backend/src/database/schema/sessions.ts +15 -0
- package/template/backend/src/database/schema/users.ts +7 -3
- package/template/backend/src/database/schema/verifications.ts +11 -0
- package/template/backend/{env.d.ts → src/env.d.ts} +2 -0
- package/template/backend/src/{server.ts → main.ts} +13 -19
- package/template/backend/src/orpc/index.ts +65 -0
- package/template/backend/src/orpc/middlewares/auth.ts +33 -0
- package/template/backend/src/orpc/middlewares/index.ts +1 -0
- package/template/backend/src/orpc/router/auth.ts +54 -0
- package/template/backend/src/{router → orpc/router}/index.ts +2 -2
- package/template/backend/src/{logger.ts → utils/logger.ts} +1 -3
- package/template/frontend/package.json +12 -13
- package/template/frontend/src/App.vue +96 -10
- package/template/frontend/src/composables/auth.ts +31 -0
- package/template/frontend/src/composables/index.ts +1 -0
- package/template/frontend/src/lib/orpc.ts +6 -1
- package/template/knip.config.ts +1 -1
- package/template/package.json +9 -9
- package/template/backend/src/config/database.ts +0 -11
- package/template/backend/src/config/drizzle.ts +0 -10
- package/template/backend/src/config/logger.ts +0 -19
- package/template/backend/src/config/server.ts +0 -40
- package/template/backend/src/database/migrations/0000_white_bishop.sql +0 -6
- package/template/backend/src/lib/orpc.ts +0 -10
- package/template/backend/src/router/welcome.ts +0 -11
- package/template/frontend/src/queries/index.ts +0 -1
- package/template/frontend/src/queries/welcome.ts +0 -10
- package/template/frontend/src/utils.ts +0 -1
- /package/template/frontend/{env.d.ts → src/env.d.ts} +0 -0
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const rpcHandler = new RPCHandler(router, {
|
|
13
|
-
plugins: [new CORSPlugin(cors)],
|
|
14
|
-
interceptors: [
|
|
15
|
-
onError((error) => {
|
|
16
|
-
logger.error(error)
|
|
17
|
-
}),
|
|
18
|
-
],
|
|
19
|
-
})
|
|
3
|
+
import { createBetterAuth } from '@backend/auth'
|
|
4
|
+
import { db } from '@backend/database'
|
|
5
|
+
import { createRpcHandler } from '@backend/orpc'
|
|
6
|
+
import { router } from '@backend/orpc/router'
|
|
7
|
+
import { logger } from '@backend/utils/logger'
|
|
8
|
+
|
|
9
|
+
const auth = createBetterAuth({ db, logger })
|
|
10
|
+
const rpcHandler = createRpcHandler(router)
|
|
20
11
|
|
|
21
12
|
const server = Bun.serve({
|
|
22
|
-
hostname,
|
|
23
|
-
port,
|
|
13
|
+
hostname: import.meta.env.HOST ?? '0.0.0.0',
|
|
14
|
+
port: import.meta.env.PORT ?? 4000,
|
|
24
15
|
async fetch(request) {
|
|
25
16
|
const { matched, response } = await rpcHandler.handle(request, {
|
|
26
17
|
prefix: '/rpc',
|
|
27
18
|
context: {
|
|
19
|
+
auth,
|
|
28
20
|
db,
|
|
29
21
|
logger,
|
|
30
22
|
},
|
|
@@ -43,6 +35,8 @@ const server = Bun.serve({
|
|
|
43
35
|
|
|
44
36
|
logger.info(`Listening on ${server.url}`)
|
|
45
37
|
|
|
38
|
+
// Graceful Shutdown
|
|
39
|
+
|
|
46
40
|
async function gracefulShutdown() {
|
|
47
41
|
logger.info('Gracefully shutting down...')
|
|
48
42
|
await server.stop()
|
|
@@ -0,0 +1,65 @@
|
|
|
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)
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './auth'
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
})
|
|
@@ -9,26 +9,25 @@
|
|
|
9
9
|
"preview": "vite preview --open"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@kevinmarrec/vue-i18n": "^1.
|
|
13
|
-
"@orpc/client": "^1.8.
|
|
12
|
+
"@kevinmarrec/vue-i18n": "^1.1.0",
|
|
13
|
+
"@orpc/client": "^1.8.9",
|
|
14
14
|
"@orpc/tanstack-query": "1.6.4",
|
|
15
|
-
"@tanstack/vue-query": "^5.
|
|
16
|
-
"@unhead/vue": "^2.0.
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"vue": "^3.5.20"
|
|
15
|
+
"@tanstack/vue-query": "^5.89.0",
|
|
16
|
+
"@unhead/vue": "^2.0.17",
|
|
17
|
+
"unocss": "^66.5.1",
|
|
18
|
+
"vue": "^3.5.21"
|
|
20
19
|
},
|
|
21
20
|
"devDependencies": {
|
|
22
|
-
"@kevinmarrec/unocss-config": "^1.
|
|
23
|
-
"@kevinmarrec/vite-plugin-dark-mode": "^1.
|
|
21
|
+
"@kevinmarrec/unocss-config": "^1.3.0",
|
|
22
|
+
"@kevinmarrec/vite-plugin-dark-mode": "^1.1.0",
|
|
24
23
|
"@modyfi/vite-plugin-yaml": "^1.1.1",
|
|
25
|
-
"@unhead/addons": "^2.0.
|
|
26
|
-
"@unocss/vite": "^66.5.
|
|
24
|
+
"@unhead/addons": "^2.0.17",
|
|
25
|
+
"@unocss/vite": "^66.5.1",
|
|
27
26
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
28
27
|
"beasties": "^0.3.5",
|
|
29
28
|
"rollup-plugin-visualizer": "^6.0.3",
|
|
30
|
-
"vite": "^7.1.
|
|
31
|
-
"vite-plugin-vue-devtools": "^8.0.
|
|
29
|
+
"vite": "^7.1.6",
|
|
30
|
+
"vite-plugin-vue-devtools": "^8.0.2",
|
|
32
31
|
"vite-ssg": "^28.1.0",
|
|
33
32
|
"vite-tsconfig-paths": "^5.1.4"
|
|
34
33
|
}
|
|
@@ -1,25 +1,111 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useHead, useI18n } from '@frontend/composables'
|
|
3
|
-
import {
|
|
2
|
+
import { useAuth, useHead, useI18n } from '@frontend/composables'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
4
|
|
|
5
5
|
const { t } = useI18n()
|
|
6
|
-
|
|
7
6
|
useHead({
|
|
8
7
|
title: () => t('title'),
|
|
9
8
|
})
|
|
10
9
|
|
|
11
|
-
const {
|
|
10
|
+
const { user, signUp, signIn, signOut } = useAuth()
|
|
11
|
+
|
|
12
|
+
const email = ref('')
|
|
13
|
+
const password = ref('')
|
|
12
14
|
</script>
|
|
13
15
|
|
|
14
16
|
<template>
|
|
15
17
|
<div class="grid h-full place-items-center">
|
|
16
18
|
<div class="flex flex-col gap-4 items-center">
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
<!-- Login Form (when user is not logged in) -->
|
|
20
|
+
<div v-if="!user" class="mx-auto max-w-md w-full">
|
|
21
|
+
<div class="p-8 rounded-lg bg-white shadow-lg dark:bg-gray-800">
|
|
22
|
+
<h2 class="text-2xl text-gray-900 font-bold mb-6 text-center dark:text-white">
|
|
23
|
+
Welcome Back
|
|
24
|
+
</h2>
|
|
25
|
+
|
|
26
|
+
<form class="space-y-4" @submit.prevent>
|
|
27
|
+
<div>
|
|
28
|
+
<label for="email" class="text-sm text-gray-700 font-medium mb-2 block dark:text-gray-300">
|
|
29
|
+
Email
|
|
30
|
+
</label>
|
|
31
|
+
<input
|
|
32
|
+
id="email"
|
|
33
|
+
v-model="email"
|
|
34
|
+
type="email"
|
|
35
|
+
class="px-3 py-2 border border-gray-300 rounded-md w-full shadow-sm dark:text-white focus:outline-none dark:border-gray-600 focus:border-blue-500 dark:bg-gray-700 focus:ring-2 focus:ring-blue-500"
|
|
36
|
+
placeholder="Enter your email"
|
|
37
|
+
required
|
|
38
|
+
>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div>
|
|
42
|
+
<label for="password" class="text-sm text-gray-700 font-medium mb-2 block dark:text-gray-300">
|
|
43
|
+
Password
|
|
44
|
+
</label>
|
|
45
|
+
<input
|
|
46
|
+
id="password"
|
|
47
|
+
v-model="password"
|
|
48
|
+
type="password"
|
|
49
|
+
class="px-3 py-2 border border-gray-300 rounded-md w-full shadow-sm dark:text-white focus:outline-none dark:border-gray-600 focus:border-blue-500 dark:bg-gray-700 focus:ring-2 focus:ring-blue-500"
|
|
50
|
+
placeholder="Enter your password"
|
|
51
|
+
required
|
|
52
|
+
>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div v-if="signIn.error.value || signUp.error.value" class="text-sm text-red-600 dark:text-red-400">
|
|
56
|
+
{{ signIn.error.value || signUp.error.value }}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="flex space-x-3">
|
|
60
|
+
<button
|
|
61
|
+
:disabled="signIn.isPending.value"
|
|
62
|
+
class="text-white font-medium px-4 py-2 rounded-md bg-blue-600 flex-1 transition-colors duration-200 disabled:bg-blue-400 hover:bg-blue-700"
|
|
63
|
+
@click="signIn.mutate({ email, password })"
|
|
64
|
+
>
|
|
65
|
+
<span v-if="signIn.isPending.value">Signing In...</span>
|
|
66
|
+
<span v-else>Sign In</span>
|
|
67
|
+
</button>
|
|
68
|
+
|
|
69
|
+
<button
|
|
70
|
+
:disabled="signUp.isPending.value"
|
|
71
|
+
class="text-white font-medium px-4 py-2 rounded-md bg-green-600 flex-1 transition-colors duration-200 disabled:bg-green-400 hover:bg-green-700"
|
|
72
|
+
@click="signUp.mutate({ email, password, name: 'User' })"
|
|
73
|
+
>
|
|
74
|
+
<span v-if="signUp.isPending.value">Signing Up...</span>
|
|
75
|
+
<span v-else>Sign Up</span>
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</form>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- User Dashboard (when user is logged in) -->
|
|
83
|
+
<div v-else class="mx-auto max-w-md w-full">
|
|
84
|
+
<div class="p-8 text-center rounded-lg bg-white shadow-lg dark:bg-gray-800">
|
|
85
|
+
<div class="mb-6">
|
|
86
|
+
<div class="mx-auto mb-4 rounded-full flex h-20 w-20 items-center justify-center from-blue-500 to-purple-600 bg-gradient-to-r">
|
|
87
|
+
<span class="text-2xl text-white font-bold">
|
|
88
|
+
{{ user.name?.charAt(0).toUpperCase() || 'U' }}
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
<h2 class="text-2xl text-gray-900 font-bold dark:text-white">
|
|
92
|
+
Welcome, {{ user.name || 'User' }}!
|
|
93
|
+
</h2>
|
|
94
|
+
<p class="text-gray-600 mt-2 dark:text-gray-400">
|
|
95
|
+
{{ user.email }}
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<button
|
|
100
|
+
:disabled="signOut.isPending.value"
|
|
101
|
+
class="text-white font-medium px-4 py-2 rounded-md bg-red-600 w-full transition-colors duration-200 disabled:bg-red-400 hover:bg-red-700"
|
|
102
|
+
@click="signOut.mutate({})"
|
|
103
|
+
>
|
|
104
|
+
<span v-if="signOut.isPending.value">Signing Out...</span>
|
|
105
|
+
<span v-else>Sign Out</span>
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
23
109
|
</div>
|
|
24
110
|
</div>
|
|
25
111
|
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type { Router, RouterClient } from '@backend/router'
|
|
1
|
+
import type { Router, RouterClient } from '@backend/orpc/router'
|
|
2
2
|
import { createORPCClient } from '@orpc/client'
|
|
3
3
|
import { RPCLink } from '@orpc/client/fetch'
|
|
4
4
|
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
|
|
5
5
|
|
|
6
6
|
const link = new RPCLink({
|
|
7
7
|
url: import.meta.env.VITE_API_URL,
|
|
8
|
+
fetch: (request, init) =>
|
|
9
|
+
globalThis.fetch(request, {
|
|
10
|
+
...init,
|
|
11
|
+
credentials: 'include',
|
|
12
|
+
}),
|
|
8
13
|
})
|
|
9
14
|
|
|
10
15
|
const client: RouterClient<Router> = createORPCClient(link)
|
package/template/knip.config.ts
CHANGED
package/template/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "app",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": true,
|
|
5
|
-
"packageManager": "bun@1.2.
|
|
5
|
+
"packageManager": "bun@1.2.22",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "lts/*"
|
|
8
8
|
},
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"lint:inspect": "bunx @eslint/config-inspector"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@kevinmarrec/eslint-config": "^1.
|
|
25
|
-
"@kevinmarrec/stylelint-config": "^1.
|
|
26
|
-
"@kevinmarrec/tsconfig": "^1.
|
|
24
|
+
"@kevinmarrec/eslint-config": "^1.5.0",
|
|
25
|
+
"@kevinmarrec/stylelint-config": "^1.2.0",
|
|
26
|
+
"@kevinmarrec/tsconfig": "^1.1.0",
|
|
27
27
|
"concurrently": "^9.2.1",
|
|
28
|
-
"eslint": "^9.
|
|
29
|
-
"knip": "^5.63.
|
|
30
|
-
"stylelint": "^16.
|
|
31
|
-
"taze": "^19.
|
|
28
|
+
"eslint": "^9.36.0",
|
|
29
|
+
"knip": "^5.63.1",
|
|
30
|
+
"stylelint": "^16.24.0",
|
|
31
|
+
"taze": "^19.7.0",
|
|
32
32
|
"typescript": "~5.9.2",
|
|
33
|
-
"vue-tsc": "^3.0.
|
|
33
|
+
"vue-tsc": "^3.0.7"
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as v from 'valibot'
|
|
2
|
-
|
|
3
|
-
const schema = v.object({
|
|
4
|
-
level: v.optional(v.union([
|
|
5
|
-
v.literal('trace'),
|
|
6
|
-
v.literal('debug'),
|
|
7
|
-
v.literal('info'),
|
|
8
|
-
v.literal('warn'),
|
|
9
|
-
v.literal('error'),
|
|
10
|
-
v.literal('fatal'),
|
|
11
|
-
v.literal('silent'),
|
|
12
|
-
]), 'info'),
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
const config = v.parse(schema, {
|
|
16
|
-
level: import.meta.env.LOG_LEVEL,
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
export const level = config.level
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as v from 'valibot'
|
|
2
|
-
|
|
3
|
-
const schema = v.object({
|
|
4
|
-
cors: v.object({
|
|
5
|
-
origin: v.optional(
|
|
6
|
-
v.pipe(
|
|
7
|
-
v.string(),
|
|
8
|
-
v.minLength(1),
|
|
9
|
-
v.transform(value => value.split(',')),
|
|
10
|
-
),
|
|
11
|
-
'*',
|
|
12
|
-
),
|
|
13
|
-
}),
|
|
14
|
-
hostname: v.optional(
|
|
15
|
-
v.pipe(
|
|
16
|
-
v.string(),
|
|
17
|
-
v.minLength(1),
|
|
18
|
-
),
|
|
19
|
-
'localhost',
|
|
20
|
-
),
|
|
21
|
-
port: v.optional(v.pipe(
|
|
22
|
-
v.string(),
|
|
23
|
-
v.transform(value => +value),
|
|
24
|
-
v.number(),
|
|
25
|
-
v.minValue(3000),
|
|
26
|
-
v.maxValue(65535),
|
|
27
|
-
), '4000'),
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const config = v.parse(schema, {
|
|
31
|
-
cors: {
|
|
32
|
-
origin: import.meta.env.ALLOWED_ORIGINS,
|
|
33
|
-
},
|
|
34
|
-
hostname: import.meta.env.HOST,
|
|
35
|
-
port: import.meta.env.PORT,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
export const cors = config.cors
|
|
39
|
-
export const hostname = config.hostname
|
|
40
|
-
export const port = config.port
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { users } from '@backend/database/schema'
|
|
2
|
-
import { pub } from '@backend/lib/orpc'
|
|
3
|
-
import * as v from 'valibot'
|
|
4
|
-
|
|
5
|
-
export const welcome = pub
|
|
6
|
-
.input(v.string())
|
|
7
|
-
.output(v.string())
|
|
8
|
-
.handler(async ({ input, context: { db } }) => {
|
|
9
|
-
const count = await db.$count(users)
|
|
10
|
-
return `Hello ${input} (${count})`
|
|
11
|
-
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './welcome'
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { orpc } from '@frontend/lib/orpc'
|
|
2
|
-
import { get } from '@frontend/utils'
|
|
3
|
-
import { useQuery } from '@tanstack/vue-query'
|
|
4
|
-
import { computed, type MaybeRef } from 'vue'
|
|
5
|
-
|
|
6
|
-
export function useWelcome(text: MaybeRef<string>) {
|
|
7
|
-
return useQuery(computed(() => orpc.welcome.queryOptions({
|
|
8
|
-
input: get(text),
|
|
9
|
-
})))
|
|
10
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { get } from '@vueuse/core'
|
|
File without changes
|