@kevinmarrec/create-app 0.6.7 → 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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1 -3
  3. package/package.json +22 -18
  4. package/template/.github/workflows/ci.yml +3 -3
  5. package/template/.scripts/dev.ts +8 -0
  6. package/template/.vscode/settings.json +1 -1
  7. package/template/client/.env +1 -0
  8. package/template/{frontend → client}/package.json +11 -9
  9. package/template/{frontend → client}/src/App.vue +9 -2
  10. package/template/client/src/composables/auth.ts +39 -0
  11. package/template/client/src/composables/content.ts +12 -0
  12. package/template/{frontend → client}/src/composables/index.ts +1 -0
  13. package/template/{frontend → client}/src/lib/orpc.ts +2 -2
  14. package/template/compose.yaml +5 -5
  15. package/template/knip.config.ts +11 -6
  16. package/template/package.json +10 -10
  17. package/template/server/.env.development +4 -0
  18. package/template/{backend → server}/package.json +7 -7
  19. package/template/{backend → server}/src/auth/index.ts +3 -1
  20. package/template/{backend → server}/src/database/drizzle/config.ts +2 -1
  21. package/template/server/src/database/index.ts +20 -0
  22. package/template/server/src/database/migrations/0000_init.sql +50 -0
  23. package/template/{backend → server}/src/database/migrations/meta/0000_snapshot.json +92 -105
  24. package/template/server/src/database/migrations/meta/_journal.json +13 -0
  25. package/template/server/src/database/schema/accounts.ts +20 -0
  26. package/template/server/src/database/schema/sessions.ts +15 -0
  27. package/template/server/src/database/schema/users.ts +12 -0
  28. package/template/server/src/database/schema/verifications.ts +11 -0
  29. package/template/{backend → server}/src/env.d.ts +2 -2
  30. package/template/{backend → server}/src/main.ts +20 -16
  31. package/template/server/src/orpc/context.ts +10 -0
  32. package/template/server/src/orpc/handler.ts +34 -0
  33. package/template/server/src/orpc/index.ts +18 -0
  34. package/template/server/src/orpc/middlewares/auth.ts +23 -0
  35. package/template/server/src/orpc/router/index.ts +19 -0
  36. package/template/server/src/utils/cors.ts +26 -0
  37. package/template/tsconfig.json +2 -2
  38. package/template/backend/.env.development +0 -4
  39. package/template/backend/src/database/index.ts +0 -23
  40. package/template/backend/src/database/migrations/0000_fluffy_salo.sql +0 -49
  41. package/template/backend/src/database/migrations/meta/_journal.json +0 -13
  42. package/template/backend/src/database/schema/accounts.ts +0 -20
  43. package/template/backend/src/database/schema/sessions.ts +0 -15
  44. package/template/backend/src/database/schema/users.ts +0 -12
  45. package/template/backend/src/database/schema/verifications.ts +0 -11
  46. package/template/backend/src/orpc/index.ts +0 -65
  47. package/template/backend/src/orpc/middlewares/auth.ts +0 -33
  48. package/template/backend/src/orpc/router/auth.ts +0 -54
  49. package/template/backend/src/orpc/router/index.ts +0 -9
  50. package/template/frontend/.env.development +0 -1
  51. package/template/frontend/src/composables/auth.ts +0 -31
  52. package/template/scripts/dev.ts +0 -8
  53. /package/template/{gitignore → .gitignore} +0 -0
  54. /package/template/{npmrc → .npmrc} +0 -0
  55. /package/template/{frontend → client}/index.html +0 -0
  56. /package/template/{frontend → client}/public/favicon.svg +0 -0
  57. /package/template/{frontend → client}/public/robots.txt +0 -0
  58. /package/template/{frontend → client}/src/components/.gitkeep +0 -0
  59. /package/template/{frontend → client}/src/env.d.ts +0 -0
  60. /package/template/{frontend → client}/src/locales/en.yml +0 -0
  61. /package/template/{frontend → client}/src/locales/fr.yml +0 -0
  62. /package/template/{frontend → client}/src/main.ts +0 -0
  63. /package/template/{frontend → client}/tsconfig.json +0 -0
  64. /package/template/{frontend → client}/uno.config.ts +0 -0
  65. /package/template/{frontend → client}/vite.config.ts +0 -0
  66. /package/template/{frontend → client}/wrangler.json +0 -0
  67. /package/template/{backend → server}/src/database/schema/index.ts +0 -0
  68. /package/template/{backend → server}/src/orpc/middlewares/index.ts +0 -0
  69. /package/template/{backend → server}/src/utils/logger.ts +0 -0
  70. /package/template/{backend → server}/tsconfig.json +0 -0
@@ -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
+ }
@@ -2,8 +2,8 @@
2
2
  "extends": "@kevinmarrec/tsconfig",
3
3
  "compilerOptions": {
4
4
  "paths": {
5
- "@backend/*": ["./backend/src/*"],
6
- "@frontend/*": ["./frontend/src/*"]
5
+ "@client/*": ["./client/src/*"],
6
+ "@server/*": ["./server/src/*"]
7
7
  }
8
8
  },
9
9
  "exclude": ["**/dist/**"]
@@ -1,4 +0,0 @@
1
- BETTER_AUTH_SECRET=foo
2
- BETTER_AUTH_URL=http://localhost:5137
3
- DATABASE_URL=dev.db
4
- NODE_ENV=development
@@ -1,23 +0,0 @@
1
- import { logger } from '@backend/utils/logger'
2
- import { drizzle } from 'drizzle-orm/bun-sqlite'
3
-
4
- import * as schema from './schema'
5
-
6
- export const db = drizzle(import.meta.env.DATABASE_URL, {
7
- casing: 'snake_case',
8
- schema,
9
- logger: {
10
- logQuery: (query, params) => {
11
- let msg = `[SQL] ${query}`
12
- msg += params.length ? ` [${params}]` : ''
13
- logger.debug(msg)
14
- },
15
- },
16
- })
17
-
18
- db.run('PRAGMA journal_mode = WAL')
19
- db.run('PRAGMA journal_size_limit = 6144000')
20
- db.run('PRAGMA synchronous = NORMAL')
21
- db.run('PRAGMA foreign_keys = ON')
22
-
23
- export type Database = typeof db
@@ -1,49 +0,0 @@
1
- CREATE TABLE `users` (
2
- `id` text PRIMARY KEY NOT NULL,
3
- `name` text NOT NULL,
4
- `email` text NOT NULL,
5
- `email_verified` integer DEFAULT false NOT NULL,
6
- `image` text,
7
- `created_at` integer NOT NULL,
8
- `updated_at` integer NOT NULL
9
- );
10
- --> statement-breakpoint
11
- CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);--> statement-breakpoint
12
- CREATE TABLE `sessions` (
13
- `id` text PRIMARY KEY NOT NULL,
14
- `user_id` text NOT NULL,
15
- `token` text NOT NULL,
16
- `expires_at` integer NOT NULL,
17
- `ip_address` text,
18
- `user_agent` text,
19
- `created_at` integer NOT NULL,
20
- `updated_at` integer NOT NULL,
21
- FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
22
- );
23
- --> statement-breakpoint
24
- CREATE UNIQUE INDEX `sessions_token_unique` ON `sessions` (`token`);--> statement-breakpoint
25
- CREATE TABLE `accounts` (
26
- `id` text PRIMARY KEY NOT NULL,
27
- `user_id` text NOT NULL,
28
- `account_id` text NOT NULL,
29
- `provider_id` text NOT NULL,
30
- `access_token` text,
31
- `refresh_token` text,
32
- `access_token_expires_at` integer,
33
- `refresh_token_expires_at` integer,
34
- `scope` text,
35
- `id_token` text,
36
- `password` text,
37
- `created_at` integer NOT NULL,
38
- `updated_at` integer NOT NULL,
39
- FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
40
- );
41
- --> statement-breakpoint
42
- CREATE TABLE `verifications` (
43
- `id` text PRIMARY KEY NOT NULL,
44
- `identifier` text NOT NULL,
45
- `value` text NOT NULL,
46
- `expires_at` integer NOT NULL,
47
- `created_at` integer NOT NULL,
48
- `updated_at` integer NOT NULL
49
- );
@@ -1,13 +0,0 @@
1
- {
2
- "version": "7",
3
- "dialect": "sqlite",
4
- "entries": [
5
- {
6
- "idx": 0,
7
- "version": "6",
8
- "when": 1760742831459,
9
- "tag": "0000_fluffy_salo",
10
- "breakpoints": true
11
- }
12
- ]
13
- }
@@ -1,20 +0,0 @@
1
- import { sqliteTable } from 'drizzle-orm/sqlite-core'
2
-
3
- import { users } from './users'
4
-
5
- // https://www.better-auth.com/docs/concepts/database#account
6
- export const accounts = sqliteTable('accounts', t => ({
7
- id: t.text().primaryKey(),
8
- userId: t.text().notNull().references(() => users.id, { onDelete: 'cascade' }),
9
- accountId: t.text().notNull(),
10
- providerId: t.text().notNull(),
11
- accessToken: t.text(),
12
- refreshToken: t.text(),
13
- accessTokenExpiresAt: t.integer({ mode: 'timestamp' }),
14
- refreshTokenExpiresAt: t.integer({ mode: 'timestamp' }),
15
- scope: t.text(),
16
- idToken: t.text(),
17
- password: t.text(),
18
- createdAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
19
- updatedAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()).$onUpdate(() => new Date()),
20
- }))
@@ -1,15 +0,0 @@
1
- import { sqliteTable } from 'drizzle-orm/sqlite-core'
2
-
3
- import { users } from './users'
4
-
5
- // https://www.better-auth.com/docs/concepts/database#session
6
- export const sessions = sqliteTable('sessions', t => ({
7
- id: t.text().primaryKey(),
8
- userId: t.text().notNull().references(() => users.id, { onDelete: 'cascade' }),
9
- token: t.text().notNull().unique(),
10
- expiresAt: t.integer({ mode: 'timestamp' }).notNull(),
11
- ipAddress: t.text(),
12
- userAgent: t.text(),
13
- createdAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
14
- updatedAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()).$onUpdate(() => new Date()),
15
- }))
@@ -1,12 +0,0 @@
1
- import { sqliteTable } from 'drizzle-orm/sqlite-core'
2
-
3
- // https://www.better-auth.com/docs/concepts/database#user
4
- export const users = sqliteTable('users', t => ({
5
- id: t.text().primaryKey(),
6
- name: t.text().notNull(),
7
- email: t.text().notNull().unique(),
8
- emailVerified: t.integer({ mode: 'boolean' }).notNull().default(false),
9
- image: t.text(),
10
- createdAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
11
- updatedAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()).$onUpdate(() => new Date()),
12
- }))
@@ -1,11 +0,0 @@
1
- import { sqliteTable } from 'drizzle-orm/sqlite-core'
2
-
3
- // https://www.better-auth.com/docs/concepts/database#verification
4
- export const verifications = sqliteTable('verifications', t => ({
5
- id: t.text().primaryKey(),
6
- identifier: t.text().notNull(),
7
- value: t.text().notNull(),
8
- expiresAt: t.integer({ mode: 'timestamp' }).notNull(),
9
- createdAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
10
- updatedAt: t.integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()).$onUpdate(() => new Date()),
11
- }))
@@ -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,9 +0,0 @@
1
- import * as auth from './auth'
2
-
3
- export type { RouterClient } from '@orpc/server'
4
-
5
- export const router = {
6
- auth,
7
- }
8
-
9
- export type Router = typeof router
@@ -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
- }
@@ -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