@kevinmarrec/create-app 0.7.0 → 0.9.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 +3 -1
- package/dist/index.js +1 -3
- package/package.json +21 -19
- package/template/.docker/traefik/dynamic/tls.yml +4 -0
- package/template/.github/scripts/build-stats.md.ts +71 -0
- package/template/.github/workflows/ci.yml +19 -3
- package/template/{gitignore → .gitignore} +2 -0
- package/template/{npmrc → .npmrc} +0 -1
- package/template/.vscode/settings.json +1 -1
- package/template/api/.env.development +4 -0
- package/template/{backend → api}/package.json +9 -9
- package/template/{backend → api}/src/auth/index.ts +2 -0
- package/template/{backend → api}/src/database/index.ts +1 -1
- package/template/{backend → api}/src/env.d.ts +2 -2
- package/template/{backend → api}/src/main.ts +10 -20
- package/template/api/src/orpc/context.ts +11 -0
- package/template/api/src/orpc/index.ts +27 -0
- package/template/api/src/orpc/middlewares/auth.ts +24 -0
- package/template/api/src/orpc/plugins/error.ts +17 -0
- package/template/api/src/orpc/router/index.ts +31 -0
- package/template/api/src/utils/cors.ts +26 -0
- package/template/app/.env +1 -0
- package/template/{frontend → app}/package.json +13 -12
- package/template/{frontend → app}/src/App.vue +10 -2
- package/template/app/src/composables/auth.ts +39 -0
- package/template/app/src/composables/content.ts +13 -0
- package/template/{frontend → app}/src/composables/index.ts +1 -0
- package/template/{frontend → app}/src/lib/orpc.ts +3 -2
- package/template/{frontend → app}/src/main.ts +1 -1
- package/template/{frontend → app}/vite.config.ts +1 -1
- package/template/compose.yaml +65 -12
- package/template/docs/local-tls.md +152 -0
- package/template/knip.config.ts +5 -4
- package/template/package.json +14 -13
- 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/middlewares/index.ts +0 -1
- 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/{backend → api}/src/database/drizzle/config.ts +0 -0
- /package/template/{backend → api}/src/database/migrations/0000_init.sql +0 -0
- /package/template/{backend → api}/src/database/migrations/meta/0000_snapshot.json +0 -0
- /package/template/{backend → api}/src/database/migrations/meta/_journal.json +0 -0
- /package/template/{backend → api}/src/database/schema/accounts.ts +0 -0
- /package/template/{backend → api}/src/database/schema/index.ts +0 -0
- /package/template/{backend → api}/src/database/schema/sessions.ts +0 -0
- /package/template/{backend → api}/src/database/schema/users.ts +0 -0
- /package/template/{backend → api}/src/database/schema/verifications.ts +0 -0
- /package/template/{backend → api}/src/utils/logger.ts +0 -0
- /package/template/{backend → api}/tsconfig.json +0 -0
- /package/template/{frontend → app}/index.html +0 -0
- /package/template/{frontend → app}/public/favicon.svg +0 -0
- /package/template/{frontend → app}/public/robots.txt +0 -0
- /package/template/{frontend → app}/src/components/.gitkeep +0 -0
- /package/template/{frontend → app}/src/env.d.ts +0 -0
- /package/template/{frontend → app}/src/locales/en.yml +0 -0
- /package/template/{frontend → app}/src/locales/fr.yml +0 -0
- /package/template/{frontend → app}/tsconfig.json +0 -0
- /package/template/{frontend → app}/uno.config.ts +0 -0
- /package/template/{frontend → app}/wrangler.json +0 -0
package/README.md
CHANGED
|
@@ -12,8 +12,10 @@ CLI that scaffolds an opinionated [Bun](https://bun.sh) & [Vue](https://vuejs.or
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
> Requires [Bun](https://bun.sh) v1.
|
|
15
|
+
> Requires [Bun](https://bun.sh) v1.3 _or later_.
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
bun create @kevinmarrec/app
|
|
19
|
+
# OR
|
|
20
|
+
bunx @kevinmarrec/create-app
|
|
19
21
|
```
|
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.9.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,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevinmarrec/create-app",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"packageManager": "bun@1.3.
|
|
4
|
+
"version": "0.9.0",
|
|
5
|
+
"packageManager": "bun@1.3.2",
|
|
6
6
|
"description": "CLI that scaffolds an opinionated Bun & Vue fullstack application.",
|
|
7
7
|
"author": "Kevin Marrec <kevin@marrec.io>",
|
|
8
8
|
"license": "MIT",
|
|
@@ -18,26 +18,28 @@
|
|
|
18
18
|
],
|
|
19
19
|
"workspaces": [
|
|
20
20
|
"template",
|
|
21
|
-
"template/
|
|
22
|
-
"template/
|
|
21
|
+
"template/api",
|
|
22
|
+
"template/app"
|
|
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",
|
|
37
39
|
"check:unused": "knip -n",
|
|
38
40
|
"lint": "bun run check:eslint && bun run check:stylelint",
|
|
39
41
|
"lint:inspect": "bunx @eslint/config-inspector",
|
|
40
|
-
"playground": "bun
|
|
42
|
+
"playground": "bun run scripts/transform-compose.ts | docker compose -f -",
|
|
41
43
|
"release": "bumpp",
|
|
42
44
|
"update": "bunx taze -I -rwi",
|
|
43
45
|
"test": "vitest",
|
|
@@ -47,22 +49,22 @@
|
|
|
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.6",
|
|
57
|
+
"@kevinmarrec/stylelint-config": "^1.5.6",
|
|
58
|
+
"@kevinmarrec/tsconfig": "^1.5.6",
|
|
59
|
+
"@types/bun": "^1.3.2",
|
|
60
|
+
"@vitest/coverage-v8": "^4.0.9",
|
|
59
61
|
"bumpp": "^10.3.1",
|
|
60
|
-
"eslint": "^9.
|
|
61
|
-
"knip": "^5.
|
|
62
|
+
"eslint": "^9.39.1",
|
|
63
|
+
"knip": "^5.69.1",
|
|
62
64
|
"stylelint": "^16.25.0",
|
|
63
|
-
"tsdown": "^0.
|
|
65
|
+
"tsdown": "^0.16.4",
|
|
64
66
|
"typescript": "^5.9.3",
|
|
65
|
-
"vitest": "^4.0.
|
|
66
|
-
"vue-tsc": "^3.1.
|
|
67
|
+
"vitest": "^4.0.9",
|
|
68
|
+
"vue-tsc": "^3.1.3"
|
|
67
69
|
}
|
|
68
70
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import { parseArgs } from 'node:util'
|
|
5
|
+
|
|
6
|
+
import { filesize } from 'filesize'
|
|
7
|
+
import { x } from 'tinyexec'
|
|
8
|
+
import { glob } from 'tinyglobby'
|
|
9
|
+
|
|
10
|
+
interface FileStats {
|
|
11
|
+
file: string
|
|
12
|
+
size: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function getFileStats(directory: string) {
|
|
16
|
+
const fileStats: FileStats[] = []
|
|
17
|
+
|
|
18
|
+
for (const file of await glob(['**/*'], { cwd: directory })) {
|
|
19
|
+
const size = fs.statSync(path.join(directory, file)).size
|
|
20
|
+
fileStats.push({
|
|
21
|
+
file,
|
|
22
|
+
size,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fileStats.sort((a, b) => {
|
|
27
|
+
// Sort so that files starting with 'assets/' come first
|
|
28
|
+
if (a.file.startsWith('assets/') && !b.file.startsWith('assets/')) return -1
|
|
29
|
+
if (!a.file.startsWith('assets/') && b.file.startsWith('assets/')) return 1
|
|
30
|
+
|
|
31
|
+
// Within that, files ending with '.js' come first
|
|
32
|
+
if (a.file.endsWith('.js') && !b.file.endsWith('.js')) return -1
|
|
33
|
+
if (!a.file.endsWith('.js') && b.file.endsWith('.js')) return 1
|
|
34
|
+
|
|
35
|
+
// Otherwise sort alphabetically
|
|
36
|
+
return a.file.localeCompare(b.file)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return fileStats
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function generateFileStatsMarkdown(directory: string) {
|
|
43
|
+
const fileStats = await getFileStats(directory)
|
|
44
|
+
return [
|
|
45
|
+
'| File | Size |',
|
|
46
|
+
'| :--- | ---: |',
|
|
47
|
+
...fileStats.map(file => `| ${file.file} | ${filesize(file.size)} |`),
|
|
48
|
+
].join('\n')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
const { positionals: [directory] } = parseArgs({
|
|
53
|
+
args: process.argv.slice(2),
|
|
54
|
+
allowPositionals: true,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (!directory) {
|
|
58
|
+
process.stdout.write('Usage: analyze <directory>\n')
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await x('bun', ['run', 'build'], { nodeOptions: { cwd: directory } })
|
|
63
|
+
|
|
64
|
+
const markdownTable = await generateFileStatsMarkdown(path.join(directory, 'dist'))
|
|
65
|
+
process.stdout.write(`${markdownTable}\n`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main().catch((error) => {
|
|
69
|
+
console.error(error)
|
|
70
|
+
process.exit(1)
|
|
71
|
+
})
|
|
@@ -16,11 +16,27 @@ 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
|
|
27
|
+
project-size:
|
|
28
|
+
name: Analyze project build size
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- name: Setup Bun
|
|
32
|
+
uses: kevinmarrec/workflows/setup-bun@v1
|
|
33
|
+
|
|
34
|
+
- name: Check api build size
|
|
35
|
+
run: |
|
|
36
|
+
echo "## API" >> $GITHUB_STEP_SUMMARY
|
|
37
|
+
bun run .github/scripts/build-stats.md.ts api >> $GITHUB_STEP_SUMMARY
|
|
38
|
+
|
|
39
|
+
- name: Check app build size
|
|
40
|
+
run: |
|
|
41
|
+
echo "## App" >> $GITHUB_STEP_SUMMARY
|
|
42
|
+
bun run .github/scripts/build-stats.md.ts app >> $GITHUB_STEP_SUMMARY
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "api",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "bun --watch --no-clear-screen src/main.ts",
|
|
7
|
-
"build": "bun build src/main.ts --compile --minify --sourcemap --outfile dist/
|
|
6
|
+
"dev": "bun --watch --no-clear-screen src/main.ts | pino-pretty",
|
|
7
|
+
"build": "bun build src/main.ts --compile --minify --sourcemap --outfile dist/api",
|
|
8
8
|
"db:generate": "bun --bun run drizzle-kit generate --config src/database/drizzle/config.ts",
|
|
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.11.2",
|
|
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.14",
|
|
20
|
+
"@types/bun": "^1.3.2",
|
|
21
|
+
"drizzle-kit": "^0.31.7",
|
|
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,31 +1,21 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
2
|
|
|
3
|
-
import { createBetterAuth } from '
|
|
4
|
-
import { db } from '
|
|
5
|
-
import {
|
|
6
|
-
import { router } from '
|
|
7
|
-
import {
|
|
3
|
+
import { createBetterAuth } from './auth'
|
|
4
|
+
import { db } from './database'
|
|
5
|
+
import { createRpc } from './orpc'
|
|
6
|
+
import { router } from './orpc/router'
|
|
7
|
+
import { cors } from './utils/cors'
|
|
8
|
+
import { logger } from './utils/logger'
|
|
8
9
|
|
|
9
10
|
const auth = createBetterAuth({ db, logger })
|
|
10
|
-
const
|
|
11
|
+
const rpc = createRpc({ auth, db, logger }, router)
|
|
11
12
|
|
|
12
13
|
const server = Bun.serve({
|
|
13
14
|
hostname: import.meta.env.HOST ?? '0.0.0.0',
|
|
14
15
|
port: import.meta.env.PORT ?? 4000,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
context: {
|
|
19
|
-
auth,
|
|
20
|
-
db,
|
|
21
|
-
logger,
|
|
22
|
-
},
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
if (matched)
|
|
26
|
-
return response
|
|
27
|
-
|
|
28
|
-
return new Response('Not found', { status: 404 })
|
|
16
|
+
routes: {
|
|
17
|
+
'/auth/*': cors(auth.handler),
|
|
18
|
+
'/rpc/*': cors(rpc.handler),
|
|
29
19
|
},
|
|
30
20
|
error(error) {
|
|
31
21
|
logger.error(error)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RequestHeadersPluginContext, ResponseHeadersPluginContext } from '@orpc/server/plugins'
|
|
2
|
+
|
|
3
|
+
import type { Auth } from '../auth'
|
|
4
|
+
import type { Database } from '../database'
|
|
5
|
+
import type { Logger } from '../utils/logger'
|
|
6
|
+
|
|
7
|
+
export interface Context extends RequestHeadersPluginContext, ResponseHeadersPluginContext {
|
|
8
|
+
auth: Auth
|
|
9
|
+
db: Database
|
|
10
|
+
logger: Logger
|
|
11
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Router } from '@orpc/server'
|
|
2
|
+
import { RPCHandler } from '@orpc/server/fetch'
|
|
3
|
+
import { RequestHeadersPlugin, ResponseHeadersPlugin } from '@orpc/server/plugins'
|
|
4
|
+
|
|
5
|
+
import type { Context } from './context'
|
|
6
|
+
import { ErrorPlugin } from './plugins/error'
|
|
7
|
+
|
|
8
|
+
export function createRpc<T extends Context>(context: T, router: Router<any, T>) {
|
|
9
|
+
const handler = new RPCHandler<T>(router, {
|
|
10
|
+
plugins: [
|
|
11
|
+
new ErrorPlugin(),
|
|
12
|
+
new RequestHeadersPlugin(),
|
|
13
|
+
new ResponseHeadersPlugin(),
|
|
14
|
+
],
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
handler: async (req: Request) => {
|
|
19
|
+
const { matched, response } = await handler.handle(req, {
|
|
20
|
+
prefix: '/rpc',
|
|
21
|
+
context,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return matched ? response : new Response('Not found', { status: 404 })
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ORPCError, os } from '@orpc/server'
|
|
2
|
+
|
|
3
|
+
import type { Context } from '../context'
|
|
4
|
+
|
|
5
|
+
export const authMiddleware = os
|
|
6
|
+
.$context<Context>()
|
|
7
|
+
.middleware(async ({ context, next }) => {
|
|
8
|
+
const { response: session, headers } = await context.auth.api.getSession({
|
|
9
|
+
headers: context.reqHeaders,
|
|
10
|
+
returnHeaders: true,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
headers.forEach((v, k) => context.resHeaders?.append(k, v))
|
|
14
|
+
|
|
15
|
+
if (!session) {
|
|
16
|
+
throw new ORPCError('UNAUTHORIZED')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return next({
|
|
20
|
+
context: {
|
|
21
|
+
user: session.user,
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { onError, ORPCError } from '@orpc/server'
|
|
2
|
+
import type { StandardHandlerOptions, StandardHandlerPlugin } from '@orpc/server/standard'
|
|
3
|
+
|
|
4
|
+
import type { Context } from '../context'
|
|
5
|
+
|
|
6
|
+
export class ErrorPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
7
|
+
init(options: StandardHandlerOptions<T>): void {
|
|
8
|
+
options.clientInterceptors ??= []
|
|
9
|
+
options.clientInterceptors.unshift(onError((error, { context }) => {
|
|
10
|
+
if (error instanceof ORPCError) {
|
|
11
|
+
throw error
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
context.logger.error(error)
|
|
15
|
+
}))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { os } from '@orpc/server'
|
|
2
|
+
import * as v from 'valibot'
|
|
3
|
+
|
|
4
|
+
import type { Context } from '../context'
|
|
5
|
+
import { authMiddleware } from '../middlewares/auth'
|
|
6
|
+
|
|
7
|
+
export type { RouterClient } from '@orpc/server'
|
|
8
|
+
|
|
9
|
+
const pub = os
|
|
10
|
+
.$context<Context>()
|
|
11
|
+
.errors({
|
|
12
|
+
UNAUTHORIZED: { status: 401 },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const authed = pub
|
|
16
|
+
.use(authMiddleware)
|
|
17
|
+
|
|
18
|
+
export const router = {
|
|
19
|
+
public: pub
|
|
20
|
+
.input(v.any())
|
|
21
|
+
.handler(async () => {
|
|
22
|
+
return 'public'
|
|
23
|
+
}),
|
|
24
|
+
private: authed
|
|
25
|
+
.input(v.any())
|
|
26
|
+
.handler(async () => {
|
|
27
|
+
return 'private'
|
|
28
|
+
}),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VITE_API_URL=https://api.dev.localhost
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "app",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
@@ -9,27 +9,28 @@
|
|
|
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.
|
|
12
|
+
"@kevinmarrec/vue-i18n": "^1.1.3",
|
|
13
|
+
"@orpc/client": "^1.11.2",
|
|
14
|
+
"@orpc/tanstack-query": "1.11.2",
|
|
15
|
+
"@tanstack/query-core": "^5.90.9",
|
|
16
|
+
"@tanstack/vue-query": "^5.91.1",
|
|
17
17
|
"@unhead/vue": "^2.0.19",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"@vueuse/core": "^14.0.0",
|
|
19
|
+
"better-auth": "^1.3.34",
|
|
20
|
+
"unocss": "^66.5.6",
|
|
21
|
+
"vue": "^3.5.24"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
22
|
-
"@kevinmarrec/unocss-config": "^1.
|
|
24
|
+
"@kevinmarrec/unocss-config": "^1.5.6",
|
|
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",
|
|
26
|
-
"@unocss/vite": "^66.5.4",
|
|
27
28
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
28
29
|
"beasties": "^0.3.5",
|
|
29
30
|
"rollup-plugin-visualizer": "^6.0.5",
|
|
30
|
-
"vite": "^7.
|
|
31
|
+
"vite": "^7.2.2",
|
|
31
32
|
"vite-plugin-vue-devtools": "^8.0.3",
|
|
32
|
-
"vite-ssg": "^28.2.
|
|
33
|
+
"vite-ssg": "^28.2.2",
|
|
33
34
|
"vite-tsconfig-paths": "^5.1.4"
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useAuth, useHead, useI18n } from '@frontend/composables'
|
|
3
2
|
import { ref } from 'vue'
|
|
4
3
|
|
|
4
|
+
import { useAuth, useContent, useHead, useI18n } from '~/app/composables'
|
|
5
|
+
|
|
5
6
|
const { t } = useI18n()
|
|
6
7
|
useHead({
|
|
7
8
|
title: () => t('title'),
|
|
@@ -12,7 +13,8 @@ useHead({
|
|
|
12
13
|
}],
|
|
13
14
|
})
|
|
14
15
|
|
|
15
|
-
const { user,
|
|
16
|
+
const { user, signIn, signUp, signOut } = useAuth()
|
|
17
|
+
const { publicContent, privateContent } = useContent()
|
|
16
18
|
|
|
17
19
|
const email = ref('')
|
|
18
20
|
const password = ref('')
|
|
@@ -99,6 +101,12 @@ const password = ref('')
|
|
|
99
101
|
<p class="text-gray-600 mt-2 dark:text-gray-400">
|
|
100
102
|
{{ user.email }}
|
|
101
103
|
</p>
|
|
104
|
+
<p class="text-gray-600 mt-2 dark:text-gray-400">
|
|
105
|
+
{{ publicContent }}
|
|
106
|
+
</p>
|
|
107
|
+
<p class="text-gray-600 mt-2 dark:text-gray-400">
|
|
108
|
+
{{ privateContent }}
|
|
109
|
+
</p>
|
|
102
110
|
</div>
|
|
103
111
|
|
|
104
112
|
<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,13 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/vue-query'
|
|
2
|
+
|
|
3
|
+
import { orpc } from '~/app/lib/orpc'
|
|
4
|
+
|
|
5
|
+
export function useContent() {
|
|
6
|
+
const publicContent = useQuery(orpc.public.queryOptions({})).data
|
|
7
|
+
const privateContent = useQuery(orpc.private.queryOptions({})).data
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
publicContent,
|
|
11
|
+
privateContent,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
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'
|
|
5
4
|
|
|
5
|
+
import type { Router, RouterClient } from '~/api/orpc/router'
|
|
6
|
+
|
|
6
7
|
const link = new RPCLink({
|
|
7
|
-
url: import.meta.env.VITE_API_URL
|
|
8
|
+
url: `${import.meta.env.VITE_API_URL}/rpc`,
|
|
8
9
|
fetch: (request, init) =>
|
|
9
10
|
globalThis.fetch(request, {
|
|
10
11
|
...init,
|