create-pyro 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +32 -11
  2. package/dist/index.js +150 -30
  3. package/dist/scaffold.js +106 -0
  4. package/package.json +7 -7
  5. package/template/.dockerignore +30 -0
  6. package/template/.env.example +23 -0
  7. package/template/.gitattributes +2 -0
  8. package/template/.github/workflows/ci.yml +43 -0
  9. package/template/.vscode/extensions.json +3 -0
  10. package/template/.vscode/settings.json +8 -0
  11. package/template/README.md +57 -4
  12. package/template/apps/api/Dockerfile +46 -0
  13. package/template/apps/api/package.json +43 -0
  14. package/template/apps/api/src/app.test.ts +44 -0
  15. package/template/apps/api/src/app.ts +54 -0
  16. package/template/apps/api/src/index.ts +13 -0
  17. package/template/apps/api/src/lib/auth.ts +46 -0
  18. package/template/apps/api/src/lib/response.ts +28 -0
  19. package/template/apps/api/src/lib/session.ts +22 -0
  20. package/template/apps/api/src/middleware/auth.ts +34 -0
  21. package/template/apps/api/src/routes/auth.ts +9 -0
  22. package/template/apps/api/src/routes/index.ts +8 -0
  23. package/template/apps/api/src/routes/me.test.ts +45 -0
  24. package/template/apps/api/src/routes/me.ts +29 -0
  25. package/template/apps/api/src/test/database-url.ts +23 -0
  26. package/template/apps/api/src/test/global-setup.ts +36 -0
  27. package/template/apps/api/src/test/setup.ts +8 -0
  28. package/template/apps/api/src/types/context.ts +8 -0
  29. package/template/apps/api/tsconfig.build.json +8 -0
  30. package/template/apps/api/tsconfig.json +9 -0
  31. package/template/apps/api/tsdown.config.ts +13 -0
  32. package/template/apps/api/vitest.config.ts +11 -0
  33. package/template/apps/web/Dockerfile +49 -0
  34. package/template/apps/web/app/app.css +29 -0
  35. package/template/apps/web/app/lib/api.server.ts +13 -0
  36. package/template/apps/web/app/lib/api.test.ts +28 -0
  37. package/template/apps/web/app/lib/api.ts +47 -0
  38. package/template/apps/web/app/lib/auth-client.ts +13 -0
  39. package/template/apps/web/app/lib/env.server.ts +11 -0
  40. package/template/apps/web/app/lib/public-env.ts +17 -0
  41. package/template/apps/web/app/lib/session.server.ts +31 -0
  42. package/template/apps/web/app/lib/utils.ts +6 -0
  43. package/template/apps/web/app/root.tsx +86 -0
  44. package/template/apps/web/app/routes/dashboard.tsx +81 -0
  45. package/template/apps/web/app/routes/home.tsx +161 -0
  46. package/template/apps/web/app/routes.ts +6 -0
  47. package/template/apps/web/package.json +41 -0
  48. package/template/apps/web/public/favicon.svg +5 -0
  49. package/template/apps/web/react-router.config.ts +6 -0
  50. package/template/apps/web/tsconfig.json +23 -0
  51. package/template/apps/web/vite.config.ts +19 -0
  52. package/template/apps/web/vitest.config.ts +13 -0
  53. package/template/biome.json +53 -0
  54. package/template/compose.dev.yml +26 -0
  55. package/template/compose.yml +75 -0
  56. package/template/gitignore +32 -1
  57. package/template/infra/nginx/default.conf +33 -0
  58. package/template/package.json +23 -1
  59. package/template/packages/config/package.json +31 -0
  60. package/template/packages/config/src/index.test.ts +100 -0
  61. package/template/packages/config/src/index.ts +100 -0
  62. package/template/packages/config/tsconfig.build.json +8 -0
  63. package/template/packages/config/tsconfig.json +9 -0
  64. package/template/packages/config/tsdown.config.ts +12 -0
  65. package/template/packages/db/Dockerfile +22 -0
  66. package/template/packages/db/drizzle/0000_init.sql +53 -0
  67. package/template/packages/db/drizzle/meta/0000_snapshot.json +376 -0
  68. package/template/packages/db/drizzle/meta/_journal.json +13 -0
  69. package/template/packages/db/drizzle.config.ts +12 -0
  70. package/template/packages/db/package.json +38 -0
  71. package/template/packages/db/src/client.ts +12 -0
  72. package/template/packages/db/src/index.ts +2 -0
  73. package/template/packages/db/src/schema/auth.ts +96 -0
  74. package/template/packages/db/src/schema/index.ts +1 -0
  75. package/template/packages/db/tsconfig.build.json +7 -0
  76. package/template/packages/db/tsconfig.json +9 -0
  77. package/template/packages/db/tsdown.config.ts +12 -0
  78. package/template/packages/shared/package.json +32 -0
  79. package/template/packages/shared/src/index.ts +2 -0
  80. package/template/packages/shared/src/schemas/auth.test.ts +32 -0
  81. package/template/packages/shared/src/schemas/auth.ts +22 -0
  82. package/template/packages/shared/src/schemas/index.ts +1 -0
  83. package/template/packages/shared/src/types/api.ts +15 -0
  84. package/template/packages/shared/src/types/index.ts +2 -0
  85. package/template/packages/shared/src/types/user.ts +10 -0
  86. package/template/packages/shared/tsconfig.build.json +8 -0
  87. package/template/packages/shared/tsconfig.json +9 -0
  88. package/template/packages/shared/tsdown.config.ts +12 -0
  89. package/template/pnpm-workspace.yaml +25 -0
  90. package/template/tsconfig.json +19 -0
  91. package/template/turbo.json +27 -0
@@ -0,0 +1,75 @@
1
+ name: my-pyro-app
2
+
3
+ services:
4
+ postgres:
5
+ image: postgres:18-alpine
6
+ restart: unless-stopped
7
+ environment:
8
+ POSTGRES_USER: ${POSTGRES_USER:?see .env.example}
9
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?see .env.example}
10
+ POSTGRES_DB: ${POSTGRES_DB:?see .env.example}
11
+ volumes:
12
+ - postgres_data:/var/lib/postgresql
13
+ healthcheck:
14
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
15
+ interval: 10s
16
+ timeout: 5s
17
+ retries: 5
18
+
19
+ migrator:
20
+ build:
21
+ context: .
22
+ dockerfile: packages/db/Dockerfile
23
+ environment:
24
+ NODE_ENV: production
25
+ DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
26
+ depends_on:
27
+ postgres:
28
+ condition: service_healthy
29
+ restart: "no"
30
+
31
+ api:
32
+ build:
33
+ context: .
34
+ dockerfile: apps/api/Dockerfile
35
+ environment:
36
+ NODE_ENV: production
37
+ DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
38
+ API_PORT: 3002
39
+ BETTER_AUTH_URL: ${PUBLIC_ORIGIN:-http://localhost}
40
+ BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?see .env.example}
41
+ CORS_ORIGIN: ${PUBLIC_ORIGIN:-http://localhost}
42
+ depends_on:
43
+ migrator:
44
+ condition: service_completed_successfully
45
+ restart: unless-stopped
46
+
47
+ web:
48
+ build:
49
+ context: .
50
+ dockerfile: apps/web/Dockerfile
51
+ environment:
52
+ NODE_ENV: production
53
+ PORT: 3000
54
+ PUBLIC_API_URL: ${PUBLIC_ORIGIN:-http://localhost}
55
+ INTERNAL_API_URL: http://api:3002
56
+ depends_on:
57
+ api:
58
+ condition: service_healthy
59
+ restart: unless-stopped
60
+
61
+ nginx:
62
+ image: nginx:1.30-alpine
63
+ ports:
64
+ - "80:80"
65
+ volumes:
66
+ - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
67
+ depends_on:
68
+ web:
69
+ condition: service_healthy
70
+ api:
71
+ condition: service_healthy
72
+ restart: unless-stopped
73
+
74
+ volumes:
75
+ postgres_data:
@@ -1,2 +1,33 @@
1
- # dependencies
1
+ # deps
2
2
  node_modules/
3
+ .pnpm-store/
4
+
5
+ # output
6
+ dist/
7
+
8
+ # react router
9
+ .react-router/
10
+
11
+ # env
12
+ .env*
13
+ !.env.example
14
+
15
+ # logs
16
+ logs/
17
+ *.log
18
+ npm-debug.log*
19
+ pnpm-debug.log*
20
+
21
+ # editor
22
+ .vscode/*
23
+ !.vscode/extensions.json
24
+ !.vscode/settings.json
25
+
26
+ # turbo
27
+ .turbo
28
+
29
+ # misc
30
+ .DS_Store
31
+ Thumbs.db
32
+ *.local
33
+ *.tsbuildinfo
@@ -0,0 +1,33 @@
1
+ server {
2
+ listen 80 default_server;
3
+ server_name _;
4
+
5
+ client_max_body_size 10m;
6
+
7
+ # Docker's embedded DNS. Resolving per request (via the variables below)
8
+ # lets nginx start before the upstreams and survive their restarts.
9
+ resolver 127.0.0.11 valid=10s ipv6=off;
10
+
11
+ set $api_upstream http://api:3002;
12
+ set $web_upstream http://web:3000;
13
+
14
+ location /api/ {
15
+ proxy_pass $api_upstream;
16
+ proxy_http_version 1.1;
17
+ proxy_set_header Host $host;
18
+ proxy_set_header X-Real-IP $remote_addr;
19
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
20
+ proxy_set_header X-Forwarded-Proto $scheme;
21
+ }
22
+
23
+ location / {
24
+ proxy_pass $web_upstream;
25
+ proxy_http_version 1.1;
26
+ proxy_set_header Host $host;
27
+ proxy_set_header X-Real-IP $remote_addr;
28
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
29
+ proxy_set_header X-Forwarded-Proto $scheme;
30
+ proxy_set_header Upgrade $http_upgrade;
31
+ proxy_set_header Connection "upgrade";
32
+ }
33
+ }
@@ -2,5 +2,27 @@
2
2
  "name": "my-pyro-app",
3
3
  "private": true,
4
4
  "version": "0.1.0",
5
- "type": "module"
5
+ "type": "module",
6
+ "packageManager": "pnpm@12.15.0",
7
+ "scripts": {
8
+ "dev": "turbo dev",
9
+ "build": "turbo build",
10
+ "start": "turbo start",
11
+ "lint": "biome check --write",
12
+ "lint:ci": "biome ci",
13
+ "typecheck": "turbo typecheck",
14
+ "test": "turbo test",
15
+ "db:generate": "turbo build --filter=./packages/config --output-logs=errors-only && pnpm -F db generate",
16
+ "db:migrate": "turbo build --filter=./packages/config --output-logs=errors-only && pnpm -F db migrate",
17
+ "db:push": "turbo build --filter=./packages/config --output-logs=errors-only && pnpm -F db push",
18
+ "db:studio": "turbo build --filter=./packages/config --output-logs=errors-only && pnpm -F db studio"
19
+ },
20
+ "devDependencies": {
21
+ "@biomejs/biome": "^2.6.3",
22
+ "turbo": "^2.11.10"
23
+ },
24
+ "engines": {
25
+ "node": ">=24",
26
+ "pnpm": ">=12"
27
+ }
6
28
  }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@repo/config",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsdown",
8
+ "dev": "tsdown --watch --no-clean",
9
+ "test": "vitest run",
10
+ "test:watch": "vitest",
11
+ "typecheck": "tsc"
12
+ },
13
+ "dependencies": {
14
+ "zod": "catalog:"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "catalog:",
18
+ "tsdown": "catalog:",
19
+ "typescript": "catalog:",
20
+ "vitest": "catalog:"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.mts",
25
+ "import": "./dist/index.mjs",
26
+ "default": "./dist/index.mjs"
27
+ }
28
+ },
29
+ "main": "./dist/index.mjs",
30
+ "types": "./dist/index.d.mts"
31
+ }
@@ -0,0 +1,100 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ beforeEach(() => {
4
+ vi.resetModules();
5
+ vi.unstubAllEnvs();
6
+ });
7
+
8
+ const DB_URL = "postgresql://app:password@localhost:15432/app";
9
+ const SECRET = "a".repeat(32);
10
+
11
+ describe("laziness", () => {
12
+ it("importing the module never throws, even with an empty environment", async () => {
13
+ await expect(import("./index")).resolves.toBeDefined();
14
+ });
15
+ });
16
+
17
+ describe("dbEnv", () => {
18
+ it("reads DATABASE_URL", async () => {
19
+ vi.stubEnv("DATABASE_URL", DB_URL);
20
+ const { dbEnv } = await import("./index");
21
+ expect(dbEnv.DATABASE_URL).toBe(DB_URL);
22
+ });
23
+
24
+ it("does not require variables from other scopes", async () => {
25
+ vi.stubEnv("DATABASE_URL", DB_URL);
26
+ vi.stubEnv("BETTER_AUTH_SECRET", "");
27
+ vi.stubEnv("CORS_ORIGIN", "");
28
+ const { dbEnv } = await import("./index");
29
+ expect(() => dbEnv.DATABASE_URL).not.toThrow();
30
+ });
31
+
32
+ it("explains an absent variable rather than reporting a type error", async () => {
33
+ vi.stubEnv("DATABASE_URL", undefined);
34
+ const { dbEnv } = await import("./index");
35
+ expect(() => dbEnv.DATABASE_URL).toThrow(/DATABASE_URL: required/);
36
+ expect(() => dbEnv.DATABASE_URL).not.toThrow(/received undefined/);
37
+ });
38
+ });
39
+
40
+ describe("authEnv", () => {
41
+ it("reports every problem in the scope at once", async () => {
42
+ vi.stubEnv("BETTER_AUTH_URL", "not-a-url");
43
+ vi.stubEnv("BETTER_AUTH_SECRET", "too-short");
44
+ const { authEnv } = await import("./index");
45
+ let message = "";
46
+ try {
47
+ void authEnv.BETTER_AUTH_URL;
48
+ } catch (err) {
49
+ message = err instanceof Error ? err.message : "";
50
+ }
51
+ expect(message).toContain("BETTER_AUTH_URL");
52
+ expect(message).toContain("BETTER_AUTH_SECRET");
53
+ });
54
+
55
+ it("accepts a valid scope", async () => {
56
+ vi.stubEnv("BETTER_AUTH_URL", "http://localhost:3002/");
57
+ vi.stubEnv("BETTER_AUTH_SECRET", SECRET);
58
+ const { authEnv } = await import("./index");
59
+ expect(authEnv.BETTER_AUTH_URL).toBe("http://localhost:3002");
60
+ });
61
+ });
62
+
63
+ describe("apiEnv", () => {
64
+ it("applies defaults for the port and NODE_ENV", async () => {
65
+ vi.stubEnv("CORS_ORIGIN", "http://localhost:3000");
66
+ vi.stubEnv("NODE_ENV", undefined);
67
+ vi.stubEnv("API_PORT", undefined);
68
+ const { apiEnv } = await import("./index");
69
+ expect(apiEnv.API_PORT).toBe(3002);
70
+ expect(apiEnv.NODE_ENV).toBe("development");
71
+ });
72
+
73
+ it("coerces a port from a string", async () => {
74
+ vi.stubEnv("CORS_ORIGIN", "http://localhost:3000");
75
+ vi.stubEnv("API_PORT", "4000");
76
+ const { apiEnv } = await import("./index");
77
+ expect(apiEnv.API_PORT).toBe(4000);
78
+ });
79
+
80
+ it("rejects a CORS_ORIGIN that is not an absolute URL", async () => {
81
+ vi.stubEnv("CORS_ORIGIN", "localhost:3000");
82
+ const { apiEnv } = await import("./index");
83
+ expect(() => apiEnv.CORS_ORIGIN).toThrow(/CORS_ORIGIN/);
84
+ });
85
+ });
86
+
87
+ describe("webEnv", () => {
88
+ it("rejects a PUBLIC_API_URL with a trailing /api", async () => {
89
+ vi.stubEnv("PUBLIC_API_URL", "http://localhost/api");
90
+ const { webEnv } = await import("./index");
91
+ expect(() => webEnv.PUBLIC_API_URL).toThrow(/PUBLIC_API_URL/);
92
+ });
93
+
94
+ it("defaults INTERNAL_API_URL for the non-Docker dev loop", async () => {
95
+ vi.stubEnv("PUBLIC_API_URL", "http://localhost:3002");
96
+ vi.stubEnv("INTERNAL_API_URL", undefined);
97
+ const { webEnv } = await import("./index");
98
+ expect(webEnv.INTERNAL_API_URL).toBe("http://localhost:3002");
99
+ });
100
+ });
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+
3
+ const nodeEnv = z
4
+ .enum(["development", "production", "test"])
5
+ .default("development");
6
+
7
+ const required = (hint: string) =>
8
+ z.string(`required — ${hint}`).min(1, `required — ${hint}`);
9
+
10
+ const port = (fallback: number) =>
11
+ z.coerce.number().int().min(1).max(65535).default(fallback);
12
+
13
+ const origin = (label: string) =>
14
+ required(`${label}, as an absolute http(s) URL`)
15
+ .refine((value) => {
16
+ try {
17
+ const { protocol } = new URL(value);
18
+ return protocol === "http:" || protocol === "https:";
19
+ } catch {
20
+ return false;
21
+ }
22
+ }, `${label} — must be an absolute http(s) URL, e.g. http://localhost:3000`)
23
+ .refine(
24
+ (value) => !/\/api\/?$/.test(value),
25
+ `${label} — must be an origin only; drop the trailing "/api" (call sites append their own /api/... path)`,
26
+ )
27
+ .transform((value) => value.replace(/\/+$/, ""));
28
+
29
+ const dbSchema = z.object({
30
+ DATABASE_URL: required("e.g. postgresql://app:password@localhost:15432/app"),
31
+ });
32
+
33
+ const authSchema = z.object({
34
+ BETTER_AUTH_URL: origin("origin of the API"),
35
+ BETTER_AUTH_SECRET: required("generate with `openssl rand -base64 32`").min(
36
+ 32,
37
+ "must be at least 32 chars — generate with `openssl rand -base64 32`",
38
+ ),
39
+ });
40
+
41
+ const apiSchema = z.object({
42
+ NODE_ENV: nodeEnv,
43
+ API_PORT: port(3002),
44
+ CORS_ORIGIN: origin("origin of the web app"),
45
+ });
46
+
47
+ const webSchema = z.object({
48
+ NODE_ENV: nodeEnv,
49
+ PUBLIC_API_URL: origin("public origin of the API"),
50
+ INTERNAL_API_URL: origin("internal origin of the API").default(
51
+ "http://localhost:3002",
52
+ ),
53
+ });
54
+
55
+ function formatIssues(scope: string, error: z.ZodError): string {
56
+ const lines = error.issues.map((issue) => {
57
+ const key = issue.path.join(".") || "(root)";
58
+ return ` ${key}: ${issue.message}`;
59
+ });
60
+ return `Invalid environment for "${scope}":\n${lines.join("\n")}\n\nSee .env.example at the repo root.`;
61
+ }
62
+
63
+ /**
64
+ * Validates `process.env` against `schema` on first property access, not on
65
+ * import, so a process only needs the variables of the scopes it reads.
66
+ */
67
+ function lazyEnv<T extends z.ZodType<object>>(
68
+ scope: string,
69
+ schema: T,
70
+ ): z.infer<T> {
71
+ let cached: z.infer<T> | undefined;
72
+
73
+ const load = (): z.infer<T> => {
74
+ if (cached) return cached;
75
+ const parsed = schema.safeParse(process.env);
76
+ if (!parsed.success) throw new Error(formatIssues(scope, parsed.error));
77
+ cached = parsed.data;
78
+ return cached;
79
+ };
80
+
81
+ return new Proxy({} as z.infer<T>, {
82
+ get: (_target, prop) => load()[prop as keyof z.infer<T>],
83
+ has: (_target, prop) => prop in load(),
84
+ ownKeys: () => Reflect.ownKeys(load()),
85
+ getOwnPropertyDescriptor: (_target, prop) => ({
86
+ ...Object.getOwnPropertyDescriptor(load(), prop),
87
+ configurable: true,
88
+ }),
89
+ });
90
+ }
91
+
92
+ export const dbEnv = lazyEnv("db", dbSchema);
93
+ export const authEnv = lazyEnv("auth", authSchema);
94
+ export const apiEnv = lazyEnv("api", apiSchema);
95
+ export const webEnv = lazyEnv("web", webSchema);
96
+
97
+ export type DbEnv = z.infer<typeof dbSchema>;
98
+ export type AuthEnv = z.infer<typeof authSchema>;
99
+ export type ApiEnv = z.infer<typeof apiSchema>;
100
+ export type WebEnv = z.infer<typeof webSchema>;
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": false,
5
+ "noEmit": false
6
+ },
7
+ "exclude": ["src/**/*.test.ts"]
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "rootDir": "src",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src"]
9
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ tsconfig: "tsconfig.build.json",
5
+ entry: ["src/index.ts"],
6
+ format: ["esm"],
7
+ dts: true,
8
+ sourcemap: true,
9
+ clean: true,
10
+ treeshake: true,
11
+ target: false,
12
+ });
@@ -0,0 +1,22 @@
1
+ # One-shot migrator: applies packages/db/drizzle against DATABASE_URL, then exits.
2
+ FROM node:24-alpine
3
+ WORKDIR /app
4
+
5
+ COPY package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.json ./
6
+ COPY apps/api/package.json ./apps/api/
7
+ COPY apps/web/package.json ./apps/web/
8
+ COPY packages/config/package.json ./packages/config/
9
+ COPY packages/db/package.json ./packages/db/
10
+ COPY packages/shared/package.json ./packages/shared/
11
+
12
+ RUN corepack enable && pnpm install --frozen-lockfile --filter @repo/db...
13
+
14
+ COPY packages/config ./packages/config
15
+ COPY packages/db ./packages/db
16
+
17
+ RUN pnpm --filter @repo/config build
18
+
19
+ USER node
20
+ WORKDIR /app/packages/db
21
+
22
+ CMD ["node_modules/.bin/drizzle-kit", "migrate", "--config", "drizzle.config.ts"]
@@ -0,0 +1,53 @@
1
+ CREATE TABLE "accounts" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "account_id" text NOT NULL,
4
+ "provider_id" text NOT NULL,
5
+ "user_id" text NOT NULL,
6
+ "access_token" text,
7
+ "refresh_token" text,
8
+ "id_token" text,
9
+ "access_token_expires_at" timestamp,
10
+ "refresh_token_expires_at" timestamp,
11
+ "scope" text,
12
+ "password" text,
13
+ "created_at" timestamp DEFAULT now() NOT NULL,
14
+ "updated_at" timestamp DEFAULT now() NOT NULL
15
+ );
16
+ --> statement-breakpoint
17
+ CREATE TABLE "sessions" (
18
+ "id" text PRIMARY KEY NOT NULL,
19
+ "expires_at" timestamp NOT NULL,
20
+ "token" text NOT NULL,
21
+ "ip_address" text,
22
+ "user_agent" text,
23
+ "user_id" text NOT NULL,
24
+ "created_at" timestamp DEFAULT now() NOT NULL,
25
+ "updated_at" timestamp DEFAULT now() NOT NULL,
26
+ CONSTRAINT "sessions_token_unique" UNIQUE("token")
27
+ );
28
+ --> statement-breakpoint
29
+ CREATE TABLE "users" (
30
+ "id" text PRIMARY KEY NOT NULL,
31
+ "name" text NOT NULL,
32
+ "email" text NOT NULL,
33
+ "email_verified" boolean DEFAULT false NOT NULL,
34
+ "image" text,
35
+ "created_at" timestamp DEFAULT now() NOT NULL,
36
+ "updated_at" timestamp DEFAULT now() NOT NULL,
37
+ CONSTRAINT "users_email_unique" UNIQUE("email")
38
+ );
39
+ --> statement-breakpoint
40
+ CREATE TABLE "verifications" (
41
+ "id" text PRIMARY KEY NOT NULL,
42
+ "identifier" text NOT NULL,
43
+ "value" text NOT NULL,
44
+ "expires_at" timestamp NOT NULL,
45
+ "created_at" timestamp DEFAULT now() NOT NULL,
46
+ "updated_at" timestamp DEFAULT now() NOT NULL
47
+ );
48
+ --> statement-breakpoint
49
+ ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
50
+ ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
51
+ CREATE INDEX "accounts_user_id_idx" ON "accounts" USING btree ("user_id");--> statement-breakpoint
52
+ CREATE INDEX "sessions_user_id_idx" ON "sessions" USING btree ("user_id");--> statement-breakpoint
53
+ CREATE INDEX "verifications_identifier_idx" ON "verifications" USING btree ("identifier");