@usequark/quark-create-app 1.23.11

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 (237) hide show
  1. package/README.md +210 -0
  2. package/package.json +56 -0
  3. package/src/dependabot-config.test.js +128 -0
  4. package/src/deploy/adapters/iac.js +203 -0
  5. package/src/deploy/adapters/index.js +27 -0
  6. package/src/deploy/adapters/railway.js +508 -0
  7. package/src/deploy/adapters/railway.test.js +318 -0
  8. package/src/deploy/contract.js +59 -0
  9. package/src/deploy/deploy.integration.test.js +183 -0
  10. package/src/deploy/deploy.js +430 -0
  11. package/src/deploy/deploy.test.js +105 -0
  12. package/src/deploy/discovery.js +115 -0
  13. package/src/deploy/discovery.test.js +141 -0
  14. package/src/deploy/index.js +38 -0
  15. package/src/deploy/inspect.js +130 -0
  16. package/src/index.js +2920 -0
  17. package/src/mobile-template.test.js +232 -0
  18. package/src/prepare-hooks.test.js +535 -0
  19. package/src/scaffold-guards.test.js +140 -0
  20. package/src/scaffold-output.test.js +228 -0
  21. package/src/template-scripts.test.js +160 -0
  22. package/src/utils.js +77 -0
  23. package/src/utils.test.js +95 -0
  24. package/src/worker-dockerfile.test.js +85 -0
  25. package/templates/base-project/.cursor/rules/quark.mdc +172 -0
  26. package/templates/base-project/.dockerignore +14 -0
  27. package/templates/base-project/.env.railway.example +87 -0
  28. package/templates/base-project/.github/copilot-instructions.md +65 -0
  29. package/templates/base-project/.github/dependabot.yml +31 -0
  30. package/templates/base-project/.github/skills/project-context/SKILL.md +109 -0
  31. package/templates/base-project/.github/workflows/ci.yml +116 -0
  32. package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
  33. package/templates/base-project/.github/workflows/install-windows.yml +27 -0
  34. package/templates/base-project/.github/workflows/release.yml +67 -0
  35. package/templates/base-project/.railway/railway.ts +40 -0
  36. package/templates/base-project/CLAUDE.md +163 -0
  37. package/templates/base-project/MAIN.md +21 -0
  38. package/templates/base-project/README.md +164 -0
  39. package/templates/base-project/apps/web/.env.example +99 -0
  40. package/templates/base-project/apps/web/Dockerfile +38 -0
  41. package/templates/base-project/apps/web/biome.json +8 -0
  42. package/templates/base-project/apps/web/jsconfig.json +9 -0
  43. package/templates/base-project/apps/web/next.config.js +154 -0
  44. package/templates/base-project/apps/web/package.json +42 -0
  45. package/templates/base-project/apps/web/postcss.config.mjs +7 -0
  46. package/templates/base-project/apps/web/public/.well-known/security.txt +3 -0
  47. package/templates/base-project/apps/web/public/quark.svg +47 -0
  48. package/templates/base-project/apps/web/scripts/prepare-standalone.mjs +71 -0
  49. package/templates/base-project/apps/web/src/app/_components/AsciiPanel.js +103 -0
  50. package/templates/base-project/apps/web/src/app/_components/HealthIndicator.js +85 -0
  51. package/templates/base-project/apps/web/src/app/_components/HomeThemeToggle.js +63 -0
  52. package/templates/base-project/apps/web/src/app/_components/QuarkAnimation.js +168 -0
  53. package/templates/base-project/apps/web/src/app/_components/SyncedPanels.js +212 -0
  54. package/templates/base-project/apps/web/src/app/_components/UmamiReplayRecorder.js +198 -0
  55. package/templates/base-project/apps/web/src/app/_components/UmamiWebVitals.js +39 -0
  56. package/templates/base-project/apps/web/src/app/api/auth/[...nextauth]/route.js +7 -0
  57. package/templates/base-project/apps/web/src/app/api/auth/apple/route.js +82 -0
  58. package/templates/base-project/apps/web/src/app/api/auth/google/route.js +59 -0
  59. package/templates/base-project/apps/web/src/app/api/auth/refresh/route.js +48 -0
  60. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +63 -0
  61. package/templates/base-project/apps/web/src/app/api/auth/token/route.js +44 -0
  62. package/templates/base-project/apps/web/src/app/api/csrf/route.js +40 -0
  63. package/templates/base-project/apps/web/src/app/api/device/register/route.js +57 -0
  64. package/templates/base-project/apps/web/src/app/api/error-handler.js +23 -0
  65. package/templates/base-project/apps/web/src/app/api/files/[id]/route.js +89 -0
  66. package/templates/base-project/apps/web/src/app/api/files/route.js +120 -0
  67. package/templates/base-project/apps/web/src/app/api/health/route.js +160 -0
  68. package/templates/base-project/apps/web/src/app/api/users/[id]/route.js +54 -0
  69. package/templates/base-project/apps/web/src/app/api/users/me/route.js +67 -0
  70. package/templates/base-project/apps/web/src/app/api/users/route.js +45 -0
  71. package/templates/base-project/apps/web/src/app/auth/error/page.js +83 -0
  72. package/templates/base-project/apps/web/src/app/auth/forgot-password/page.js +58 -0
  73. package/templates/base-project/apps/web/src/app/auth/provider-loading.js +25 -0
  74. package/templates/base-project/apps/web/src/app/auth/register/RegisterPageClient.js +340 -0
  75. package/templates/base-project/apps/web/src/app/auth/register/page.js +17 -0
  76. package/templates/base-project/apps/web/src/app/auth/signin/SignInPageClient.js +230 -0
  77. package/templates/base-project/apps/web/src/app/auth/signin/page.js +10 -0
  78. package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
  79. package/templates/base-project/apps/web/src/app/global-error.js +58 -0
  80. package/templates/base-project/apps/web/src/app/globals.css +1166 -0
  81. package/templates/base-project/apps/web/src/app/icon.svg +47 -0
  82. package/templates/base-project/apps/web/src/app/layout.js +57 -0
  83. package/templates/base-project/apps/web/src/app/manifest.js +12 -0
  84. package/templates/base-project/apps/web/src/app/not-found.js +39 -0
  85. package/templates/base-project/apps/web/src/app/page.js +262 -0
  86. package/templates/base-project/apps/web/src/app/robots.js +23 -0
  87. package/templates/base-project/apps/web/src/app/sitemap.js +14 -0
  88. package/templates/base-project/apps/web/src/lib/analytics/umami-before-send.js +29 -0
  89. package/templates/base-project/apps/web/src/lib/analytics/umami-config.js +95 -0
  90. package/templates/base-project/apps/web/src/lib/analytics/umami-csp.js +24 -0
  91. package/templates/base-project/apps/web/src/lib/analytics/umami-marketing.js +131 -0
  92. package/templates/base-project/apps/web/src/lib/analytics/umami-replay.js +204 -0
  93. package/templates/base-project/apps/web/src/lib/analytics/umami-web-vitals.js +237 -0
  94. package/templates/base-project/apps/web/src/lib/analytics/umami.js +57 -0
  95. package/templates/base-project/apps/web/src/lib/auth-middleware.js +82 -0
  96. package/templates/base-project/apps/web/src/lib/auth-signup.js +5 -0
  97. package/templates/base-project/apps/web/src/lib/auth.js +191 -0
  98. package/templates/base-project/apps/web/src/lib/csrf-cookie.js +11 -0
  99. package/templates/base-project/apps/web/src/lib/jwt.js +84 -0
  100. package/templates/base-project/apps/web/src/lib/load-crm-config.js +35 -0
  101. package/templates/base-project/apps/web/src/lib/normalize-auth-request.js +33 -0
  102. package/templates/base-project/apps/web/src/lib/proxy-auth.js +80 -0
  103. package/templates/base-project/apps/web/src/lib/seo/indexing.js +25 -0
  104. package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +74 -0
  105. package/templates/base-project/apps/web/src/lib/sitemap-entries.js +22 -0
  106. package/templates/base-project/apps/web/src/preflight.js +55 -0
  107. package/templates/base-project/apps/web/src/proxy.js +328 -0
  108. package/templates/base-project/biome.json +52 -0
  109. package/templates/base-project/docker-compose.override.yml +17 -0
  110. package/templates/base-project/docker-compose.yml +37 -0
  111. package/templates/base-project/docs/FIRST_FEATURE.md +74 -0
  112. package/templates/base-project/docs/START_HERE.md +70 -0
  113. package/templates/base-project/docs/adr/README.md +42 -0
  114. package/templates/base-project/package.json +58 -0
  115. package/templates/base-project/packages/db/package.json +38 -0
  116. package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +202 -0
  117. package/templates/base-project/packages/db/prisma/migrations/migration_lock.toml +3 -0
  118. package/templates/base-project/packages/db/prisma/schema.prisma +161 -0
  119. package/templates/base-project/packages/db/prisma/seed.js +152 -0
  120. package/templates/base-project/packages/db/prisma.config.js +27 -0
  121. package/templates/base-project/packages/db/src/client.js +75 -0
  122. package/templates/base-project/packages/db/src/connection.js +44 -0
  123. package/templates/base-project/packages/db/src/context.js +275 -0
  124. package/templates/base-project/packages/db/src/index.js +5 -0
  125. package/templates/base-project/packages/db/src/ping.js +66 -0
  126. package/templates/base-project/packages/db/src/queries.js +292 -0
  127. package/templates/base-project/packages/db/src/schemas.js +30 -0
  128. package/templates/base-project/packages/db/src/umami.js +299 -0
  129. package/templates/base-project/pnpm-workspace.yaml +20 -0
  130. package/templates/base-project/scripts/check-loading.mjs +49 -0
  131. package/templates/base-project/scripts/check-standards.mjs +186 -0
  132. package/templates/base-project/scripts/clean-workspace.mjs +479 -0
  133. package/templates/base-project/scripts/dev-preflight.mjs +48 -0
  134. package/templates/base-project/scripts/doctor.js +408 -0
  135. package/templates/base-project/scripts/prepare.js +490 -0
  136. package/templates/base-project/scripts/run-tests.mjs +71 -0
  137. package/templates/base-project/skills/add-dashboard/SKILL.md +28 -0
  138. package/templates/base-project/skills/add-endpoint/SKILL.md +29 -0
  139. package/templates/base-project/skills/add-model/SKILL.md +28 -0
  140. package/templates/base-project/skills/ecommerce/SKILL.md +224 -0
  141. package/templates/base-project/skills/ecommerce-cart/SKILL.md +193 -0
  142. package/templates/base-project/skills/ecommerce-catalog/SKILL.md +179 -0
  143. package/templates/base-project/skills/ecommerce-checkout/SKILL.md +195 -0
  144. package/templates/base-project/skills/i18n/SKILL.md +211 -0
  145. package/templates/base-project/skills/payment/SKILL.md +280 -0
  146. package/templates/base-project/skills/quark-skills/SKILL.md +37 -0
  147. package/templates/base-project/turbo.json +143 -0
  148. package/templates/config/package.json +20 -0
  149. package/templates/config/src/app-url.js +134 -0
  150. package/templates/config/src/environment.js +284 -0
  151. package/templates/config/src/index.js +16 -0
  152. package/templates/config/src/load-config.js +157 -0
  153. package/templates/config/src/rate-limit.js +136 -0
  154. package/templates/config/src/redis.js +43 -0
  155. package/templates/config/src/validate-env.js +532 -0
  156. package/templates/jobs/README.md +52 -0
  157. package/templates/jobs/package.json +9 -0
  158. package/templates/jobs/src/definitions.js +13 -0
  159. package/templates/jobs/src/index.js +1 -0
  160. package/templates/mobile/.env.example +16 -0
  161. package/templates/mobile/CHECKLIST.md +40 -0
  162. package/templates/mobile/README.md +130 -0
  163. package/templates/mobile/app/(app)/_layout.tsx +83 -0
  164. package/templates/mobile/app/(app)/index.tsx +147 -0
  165. package/templates/mobile/app/(app)/profile.tsx +204 -0
  166. package/templates/mobile/app/(app)/settings.tsx +80 -0
  167. package/templates/mobile/app/(auth)/_layout.tsx +10 -0
  168. package/templates/mobile/app/(auth)/sign-in.tsx +140 -0
  169. package/templates/mobile/app/(auth)/sign-up.tsx +178 -0
  170. package/templates/mobile/app/+not-found.tsx +32 -0
  171. package/templates/mobile/app/_layout.tsx +18 -0
  172. package/templates/mobile/app.json +47 -0
  173. package/templates/mobile/assets/adaptive-icon.png +0 -0
  174. package/templates/mobile/assets/icon.png +0 -0
  175. package/templates/mobile/assets/notification-icon.png +0 -0
  176. package/templates/mobile/assets/splash.png +0 -0
  177. package/templates/mobile/biome.json +27 -0
  178. package/templates/mobile/components/AuthGate.tsx +19 -0
  179. package/templates/mobile/components/ThemeProvider.tsx +23 -0
  180. package/templates/mobile/eas.json +29 -0
  181. package/templates/mobile/hooks/use-auth.ts +127 -0
  182. package/templates/mobile/lib/api-client.ts +138 -0
  183. package/templates/mobile/lib/api.ts +83 -0
  184. package/templates/mobile/lib/auth-providers-google.ts +74 -0
  185. package/templates/mobile/lib/auth-providers.ts +74 -0
  186. package/templates/mobile/lib/auth.ts +100 -0
  187. package/templates/mobile/lib/config.ts +14 -0
  188. package/templates/mobile/lib/errors.ts +46 -0
  189. package/templates/mobile/lib/notifications.ts +77 -0
  190. package/templates/mobile/lib/storage.ts +25 -0
  191. package/templates/mobile/lib/theme-tokens.ts +16 -0
  192. package/templates/mobile/package.json +37 -0
  193. package/templates/mobile/tsconfig.json +10 -0
  194. package/templates/pwa/app/_components/PWARegister.js +15 -0
  195. package/templates/pwa/app/manifest.js +32 -0
  196. package/templates/pwa/public/sw.js +72 -0
  197. package/templates/ui/README.md +44 -0
  198. package/templates/ui/package.json +21 -0
  199. package/templates/ui/src/badge.js +23 -0
  200. package/templates/ui/src/button.js +111 -0
  201. package/templates/ui/src/card.js +128 -0
  202. package/templates/ui/src/checkbox.js +20 -0
  203. package/templates/ui/src/container.js +13 -0
  204. package/templates/ui/src/dialog.js +137 -0
  205. package/templates/ui/src/error-banner.js +15 -0
  206. package/templates/ui/src/footer.js +239 -0
  207. package/templates/ui/src/form-field.integration-test.js +142 -0
  208. package/templates/ui/src/form-field.js +50 -0
  209. package/templates/ui/src/index.js +24 -0
  210. package/templates/ui/src/input.js +11 -0
  211. package/templates/ui/src/label.js +14 -0
  212. package/templates/ui/src/lightbox.integration-test.js +167 -0
  213. package/templates/ui/src/lightbox.js +243 -0
  214. package/templates/ui/src/logo.js +52 -0
  215. package/templates/ui/src/navbar.js +602 -0
  216. package/templates/ui/src/password-input.js +31 -0
  217. package/templates/ui/src/rich-text.js +514 -0
  218. package/templates/ui/src/select.js +317 -0
  219. package/templates/ui/src/skeleton.js +10 -0
  220. package/templates/ui/src/spinner.js +31 -0
  221. package/templates/ui/src/table.js +169 -0
  222. package/templates/ui/src/textarea.js +11 -0
  223. package/templates/ui/src/theme-constants.js +24 -0
  224. package/templates/ui/src/theme.js +196 -0
  225. package/templates/ui/src/toast.js +206 -0
  226. package/templates/worker/Dockerfile +64 -0
  227. package/templates/worker/README.md +56 -0
  228. package/templates/worker/package.json +36 -0
  229. package/templates/worker/src/config/pricing.js +46 -0
  230. package/templates/worker/src/handlers/email-job-validation.js +36 -0
  231. package/templates/worker/src/handlers/email.js +98 -0
  232. package/templates/worker/src/handlers/files.js +59 -0
  233. package/templates/worker/src/handlers/index.js +20 -0
  234. package/templates/worker/src/handlers/push-notification.js +234 -0
  235. package/templates/worker/src/index.js +505 -0
  236. package/templates/worker/src/lib/railway.js +438 -0
  237. package/templates/worker/src/lib/tokens.js +81 -0
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@myquark/db",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./umami": "./src/umami.js"
10
+ },
11
+ "scripts": {
12
+ "test": "node --test 'src/**/*.test.js'",
13
+ "lint": "biome format --write && biome check --write",
14
+ "db:generate": "prisma generate",
15
+ "db:migrate": "prisma migrate dev",
16
+ "db:migrate:deploy": "prisma migrate deploy",
17
+ "db:push": "prisma db push",
18
+ "db:seed": "prisma db seed",
19
+ "db:studio": "prisma studio"
20
+ },
21
+ "keywords": [],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "packageManager": "pnpm@10.12.1",
25
+ "devDependencies": {
26
+ "@faker-js/faker": "^10.6.0",
27
+ "@techstream/quark-config": "workspace:*",
28
+ "bcryptjs": "^3.0.3",
29
+ "prisma": "^7.10.0"
30
+ },
31
+ "dependencies": {
32
+ "@prisma/adapter-pg": "^7.10.0",
33
+ "@prisma/client": "^7.10.0",
34
+ "@techstream/quark-core": "^2.0.0",
35
+ "pg": "^8.23.0",
36
+ "zod": "^4.6.5"
37
+ }
38
+ }
@@ -0,0 +1,202 @@
1
+ -- CreateSchema
2
+ CREATE SCHEMA IF NOT EXISTS "public";
3
+
4
+ -- CreateEnum
5
+ CREATE TYPE "UserRole" AS ENUM ('admin', 'lead_dev', 'client_admin', 'editor', 'viewer');
6
+
7
+ -- CreateEnum
8
+ CREATE TYPE "JobStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'FAILED', 'CANCELLED');
9
+
10
+ -- CreateTable
11
+ CREATE TABLE "User" (
12
+ "id" TEXT NOT NULL,
13
+ "email" TEXT NOT NULL,
14
+ "emailVerified" TIMESTAMP(3),
15
+ "name" TEXT,
16
+ "password" TEXT,
17
+ "image" TEXT,
18
+ "role" "UserRole" NOT NULL DEFAULT 'viewer',
19
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
+ "updatedAt" TIMESTAMP(3) NOT NULL,
21
+
22
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
23
+ );
24
+
25
+ -- CreateTable
26
+ CREATE TABLE "Account" (
27
+ "id" TEXT NOT NULL,
28
+ "userId" TEXT NOT NULL,
29
+ "type" TEXT NOT NULL,
30
+ "provider" TEXT NOT NULL,
31
+ "providerAccountId" TEXT NOT NULL,
32
+ "refresh_token" TEXT,
33
+ "access_token" TEXT,
34
+ "expires_at" INTEGER,
35
+ "token_type" TEXT,
36
+ "scope" TEXT,
37
+ "id_token" TEXT,
38
+ "session_state" TEXT,
39
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
40
+ "updatedAt" TIMESTAMP(3) NOT NULL,
41
+
42
+ CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
43
+ );
44
+
45
+ -- CreateTable
46
+ CREATE TABLE "Session" (
47
+ "id" TEXT NOT NULL,
48
+ "sessionToken" TEXT NOT NULL,
49
+ "userId" TEXT NOT NULL,
50
+ "expires" TIMESTAMP(3) NOT NULL,
51
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
52
+ "updatedAt" TIMESTAMP(3) NOT NULL,
53
+
54
+ CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
55
+ );
56
+
57
+ -- CreateTable
58
+ CREATE TABLE "VerificationToken" (
59
+ "identifier" TEXT NOT NULL,
60
+ "token" TEXT NOT NULL,
61
+ "expires" TIMESTAMP(3) NOT NULL,
62
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
63
+ "updatedAt" TIMESTAMP(3) NOT NULL
64
+ );
65
+
66
+ -- CreateTable
67
+ CREATE TABLE "Job" (
68
+ "id" TEXT NOT NULL,
69
+ "queue" TEXT NOT NULL,
70
+ "name" TEXT NOT NULL,
71
+ "data" JSONB,
72
+ "status" "JobStatus" NOT NULL DEFAULT 'PENDING',
73
+ "error" TEXT,
74
+ "attempts" INTEGER NOT NULL DEFAULT 0,
75
+ "maxRetries" INTEGER NOT NULL DEFAULT 3,
76
+ "runAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
77
+ "startedAt" TIMESTAMP(3),
78
+ "completedAt" TIMESTAMP(3),
79
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
80
+ "updatedAt" TIMESTAMP(3) NOT NULL,
81
+
82
+ CONSTRAINT "Job_pkey" PRIMARY KEY ("id")
83
+ );
84
+
85
+ -- CreateTable
86
+ CREATE TABLE "File" (
87
+ "id" TEXT NOT NULL,
88
+ "filename" TEXT NOT NULL,
89
+ "originalName" TEXT NOT NULL,
90
+ "mimeType" TEXT NOT NULL,
91
+ "size" INTEGER NOT NULL,
92
+ "storageKey" TEXT NOT NULL,
93
+ "storageProvider" TEXT NOT NULL DEFAULT 'local',
94
+ "uploadedById" TEXT,
95
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
96
+ "updatedAt" TIMESTAMP(3) NOT NULL,
97
+
98
+ CONSTRAINT "File_pkey" PRIMARY KEY ("id")
99
+ );
100
+
101
+ -- CreateTable
102
+ CREATE TABLE "AuditLog" (
103
+ "id" TEXT NOT NULL,
104
+ "userId" TEXT NOT NULL,
105
+ "action" TEXT NOT NULL,
106
+ "entity" TEXT NOT NULL,
107
+ "entityId" TEXT NOT NULL,
108
+ "changes" JSONB,
109
+ "metadata" JSONB,
110
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
111
+ "updatedAt" TIMESTAMP(3) NOT NULL,
112
+
113
+ CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
114
+ );
115
+
116
+ -- CreateIndex
117
+ CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
118
+
119
+ -- CreateIndex
120
+ CREATE INDEX "User_email_idx" ON "User"("email");
121
+
122
+ -- CreateIndex
123
+ CREATE INDEX "User_createdAt_idx" ON "User"("createdAt");
124
+
125
+ -- CreateIndex
126
+ CREATE INDEX "Account_userId_idx" ON "Account"("userId");
127
+
128
+ -- CreateIndex
129
+ CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
130
+
131
+ -- CreateIndex
132
+ CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
133
+
134
+ -- CreateIndex
135
+ CREATE INDEX "Session_userId_idx" ON "Session"("userId");
136
+
137
+ -- CreateIndex
138
+ CREATE INDEX "Session_expires_idx" ON "Session"("expires");
139
+
140
+ -- CreateIndex
141
+ CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
142
+
143
+ -- CreateIndex
144
+ CREATE INDEX "VerificationToken_token_idx" ON "VerificationToken"("token");
145
+
146
+ -- CreateIndex
147
+ CREATE INDEX "VerificationToken_expires_idx" ON "VerificationToken"("expires");
148
+
149
+ -- CreateIndex
150
+ CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
151
+
152
+ -- CreateIndex
153
+ CREATE INDEX "Job_queue_idx" ON "Job"("queue");
154
+
155
+ -- CreateIndex
156
+ CREATE INDEX "Job_status_idx" ON "Job"("status");
157
+
158
+ -- CreateIndex
159
+ CREATE INDEX "Job_runAt_idx" ON "Job"("runAt");
160
+
161
+ -- CreateIndex
162
+ CREATE INDEX "Job_status_runAt_idx" ON "Job"("status", "runAt");
163
+
164
+ -- CreateIndex
165
+ CREATE INDEX "Job_createdAt_idx" ON "Job"("createdAt");
166
+
167
+ -- CreateIndex
168
+ CREATE UNIQUE INDEX "File_storageKey_key" ON "File"("storageKey");
169
+
170
+ -- CreateIndex
171
+ CREATE INDEX "File_uploadedById_idx" ON "File"("uploadedById");
172
+
173
+ -- CreateIndex
174
+ CREATE INDEX "File_mimeType_idx" ON "File"("mimeType");
175
+
176
+ -- CreateIndex
177
+ CREATE INDEX "File_createdAt_idx" ON "File"("createdAt");
178
+
179
+ -- CreateIndex
180
+ CREATE INDEX "AuditLog_userId_idx" ON "AuditLog"("userId");
181
+
182
+ -- CreateIndex
183
+ CREATE INDEX "AuditLog_action_idx" ON "AuditLog"("action");
184
+
185
+ -- CreateIndex
186
+ CREATE INDEX "AuditLog_entity_idx" ON "AuditLog"("entity");
187
+
188
+ -- CreateIndex
189
+ CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt");
190
+
191
+ -- AddForeignKey
192
+ ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
193
+
194
+ -- AddForeignKey
195
+ ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
196
+
197
+ -- AddForeignKey
198
+ ALTER TABLE "File" ADD CONSTRAINT "File_uploadedById_fkey" FOREIGN KEY ("uploadedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
199
+
200
+ -- AddForeignKey
201
+ ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
202
+
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "postgresql"
@@ -0,0 +1,161 @@
1
+ // This is your Prisma schema file,
2
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
+
4
+ // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
5
+ // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
6
+
7
+ generator client {
8
+ provider = "prisma-client"
9
+ output = "../src/generated/prisma"
10
+ }
11
+
12
+ datasource db {
13
+ provider = "postgresql"
14
+ }
15
+
16
+ enum UserRole {
17
+ admin
18
+ lead_dev
19
+ client_admin
20
+ editor
21
+ viewer
22
+ }
23
+
24
+ model User {
25
+ id String @id @default(cuid())
26
+ email String @unique
27
+ emailVerified DateTime?
28
+ name String?
29
+ password String?
30
+ image String?
31
+ role UserRole @default(viewer)
32
+ createdAt DateTime @default(now())
33
+ updatedAt DateTime @updatedAt
34
+
35
+ accounts Account[]
36
+ sessions Session[]
37
+ auditLogs AuditLog[]
38
+ files File[]
39
+
40
+ @@index([email])
41
+ @@index([createdAt])
42
+ }
43
+
44
+ // NextAuth Models
45
+ model Account {
46
+ id String @id @default(cuid())
47
+ userId String
48
+ type String
49
+ provider String
50
+ providerAccountId String
51
+ refresh_token String? @db.Text
52
+ access_token String? @db.Text
53
+ expires_at Int?
54
+ token_type String?
55
+ scope String?
56
+ id_token String? @db.Text
57
+ session_state String?
58
+ createdAt DateTime @default(now())
59
+ updatedAt DateTime @updatedAt
60
+
61
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
62
+
63
+ @@unique([provider, providerAccountId])
64
+ @@index([userId])
65
+ }
66
+
67
+ model Session {
68
+ id String @id @default(cuid())
69
+ sessionToken String @unique
70
+ userId String
71
+ expires DateTime
72
+ createdAt DateTime @default(now())
73
+ updatedAt DateTime @updatedAt
74
+
75
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
76
+
77
+ @@index([userId])
78
+ @@index([expires])
79
+ }
80
+
81
+ model VerificationToken {
82
+ identifier String
83
+ token String @unique
84
+ expires DateTime
85
+ createdAt DateTime @default(now())
86
+ updatedAt DateTime @updatedAt
87
+
88
+ @@unique([identifier, token])
89
+ @@index([token])
90
+ @@index([expires])
91
+ }
92
+
93
+ // Job Queue Model
94
+ model Job {
95
+ id String @id @default(cuid())
96
+ queue String
97
+ name String
98
+ data Json?
99
+ status JobStatus @default(PENDING)
100
+ error String?
101
+ attempts Int @default(0)
102
+ maxRetries Int @default(3)
103
+ runAt DateTime @default(now())
104
+ startedAt DateTime?
105
+ completedAt DateTime?
106
+ createdAt DateTime @default(now())
107
+ updatedAt DateTime @updatedAt
108
+
109
+ @@index([queue])
110
+ @@index([status])
111
+ @@index([runAt])
112
+ @@index([status, runAt])
113
+ @@index([createdAt])
114
+ }
115
+
116
+ enum JobStatus {
117
+ PENDING
118
+ IN_PROGRESS
119
+ COMPLETED
120
+ FAILED
121
+ CANCELLED
122
+ }
123
+
124
+ // File Upload Model
125
+ model File {
126
+ id String @id @default(cuid())
127
+ filename String
128
+ originalName String
129
+ mimeType String
130
+ size Int
131
+ storageKey String @unique
132
+ storageProvider String @default("local")
133
+ uploadedById String?
134
+ uploadedBy User? @relation(fields: [uploadedById], references: [id], onDelete: SetNull)
135
+ createdAt DateTime @default(now())
136
+ updatedAt DateTime @updatedAt
137
+
138
+ @@index([uploadedById])
139
+ @@index([mimeType])
140
+ @@index([createdAt])
141
+ }
142
+
143
+ // Audit Log Model
144
+ model AuditLog {
145
+ id String @id @default(cuid())
146
+ userId String
147
+ action String
148
+ entity String
149
+ entityId String
150
+ changes Json?
151
+ metadata Json?
152
+ createdAt DateTime @default(now())
153
+ updatedAt DateTime @updatedAt
154
+
155
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
156
+
157
+ @@index([userId])
158
+ @@index([action])
159
+ @@index([entity])
160
+ @@index([createdAt])
161
+ }
@@ -0,0 +1,152 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import bcrypt from "bcryptjs";
3
+ import { prisma } from "../src/index.js";
4
+
5
+ /**
6
+ * Minimal seed: creates a single admin user.
7
+ * Used standalone in production initial setup (SEED_PROFILE=minimal).
8
+ * Also called by seedDev to avoid duplication.
9
+ *
10
+ * Required env vars:
11
+ * ADMIN_PASSWORD - min 12 characters, auto-generates if missing (generate: openssl rand -base64 24)
12
+ * Optional env vars:
13
+ * ADMIN_EMAIL - defaults to admin@example.com
14
+ * ADMIN_NAME - defaults to Admin
15
+ */
16
+ async function seedMinimal(prisma) {
17
+ const seedProfile = process.env.SEED_PROFILE || "dev";
18
+ const adminEmail = process.env.ADMIN_EMAIL;
19
+ let adminPassword = process.env.ADMIN_PASSWORD;
20
+ const adminName = process.env.ADMIN_NAME || "Admin";
21
+
22
+ if (!adminPassword) {
23
+ const generated = randomBytes(24).toString("base64url");
24
+ console.log(
25
+ "⚠️ ADMIN_PASSWORD not set — auto-generated a random password.\n" +
26
+ " Save it if you need to log in as admin.",
27
+ );
28
+ adminPassword = generated;
29
+ }
30
+ if (adminPassword.length < 12) {
31
+ throw new Error("ADMIN_PASSWORD must be at least 12 characters.");
32
+ }
33
+
34
+ if (!adminEmail && seedProfile === "minimal") {
35
+ throw new Error(
36
+ "ADMIN_EMAIL is required when SEED_PROFILE=minimal.\n" +
37
+ " Set it in your environment to the real admin email address.",
38
+ );
39
+ }
40
+
41
+ const resolvedEmail = adminEmail || "admin@example.com";
42
+
43
+ const existing = await prisma.user.findUnique({
44
+ where: { email: resolvedEmail },
45
+ });
46
+
47
+ if (existing) {
48
+ console.log("✓ Admin user already exists");
49
+ return null;
50
+ }
51
+
52
+ const admin = await prisma.user.create({
53
+ data: {
54
+ email: resolvedEmail,
55
+ name: adminName,
56
+ role: "admin",
57
+ password: await bcrypt.hash(adminPassword, 12),
58
+ },
59
+ });
60
+
61
+ console.log(`✓ Created admin user: ${admin.email}`);
62
+ return admin;
63
+ }
64
+
65
+ /**
66
+ * Development seed: admin user + representative sample data for local testing.
67
+ * Runs by default (SEED_PROFILE=dev).
68
+ */
69
+ async function seedDev(prisma) {
70
+ const admin = await seedMinimal(prisma);
71
+
72
+ if (admin) {
73
+ const sampleUser = await prisma.user.create({
74
+ data: {
75
+ email: "user@example.com",
76
+ name: "Sample User",
77
+ role: "viewer",
78
+ image:
79
+ "https://api.dicebear.com/7.x/avataaars/svg?seed=user@example.com",
80
+ },
81
+ });
82
+
83
+ console.log(`✓ Created sample user: ${sampleUser.email}`);
84
+
85
+ await prisma.auditLog.create({
86
+ data: {
87
+ userId: admin.id,
88
+ action: "CREATE",
89
+ entity: "User",
90
+ entityId: sampleUser.id,
91
+ metadata: { email: sampleUser.email, role: sampleUser.role },
92
+ },
93
+ });
94
+
95
+ console.log("✓ Created sample audit log");
96
+
97
+ await prisma.job.create({
98
+ data: {
99
+ queue: "default",
100
+ name: "example-job",
101
+ status: "COMPLETED",
102
+ data: { message: "This is a sample job" },
103
+ completedAt: new Date(),
104
+ },
105
+ });
106
+
107
+ console.log("✓ Created sample job");
108
+ }
109
+ }
110
+
111
+ async function main() {
112
+ // Guard: require an explicit SEED_PROFILE when seeding a remote database.
113
+ // Without this, a missing SEED_PROFILE silently defaults to "dev" and seeds
114
+ // sample data onto production or staging databases.
115
+ if (!process.env.SEED_PROFILE) {
116
+ const host = process.env.POSTGRES_HOST || "";
117
+ const url = process.env.DATABASE_URL || "";
118
+ const isRemote =
119
+ (host && host !== "localhost" && host !== "127.0.0.1") ||
120
+ (url && !url.includes("localhost") && !url.includes("127.0.0.1"));
121
+ if (isRemote) {
122
+ console.error(
123
+ "❌ Set SEED_PROFILE explicitly when seeding a remote database.\n" +
124
+ " Production: SEED_PROFILE=minimal pnpm db:seed\n" +
125
+ " Staging: SEED_PROFILE=dev pnpm db:seed",
126
+ );
127
+ process.exit(1);
128
+ }
129
+ }
130
+
131
+ // SEED_PROFILE is intentionally separate from NODE_ENV.
132
+ // Railway sets NODE_ENV=production on ALL deployed services (including staging)
133
+ // for build/performance reasons, so NODE_ENV cannot reliably distinguish staging
134
+ // from production at seed time. Set SEED_PROFILE explicitly in your deploy command:
135
+ // production: SEED_PROFILE=minimal pnpm db:seed
136
+ // staging: pnpm db:seed (defaults to "dev")
137
+ const seedProfile = process.env.SEED_PROFILE || "dev";
138
+ console.log(`🌱 Seeding database with profile: "${seedProfile}"`);
139
+
140
+ if (seedProfile === "minimal") {
141
+ await seedMinimal(prisma);
142
+ } else {
143
+ await seedDev(prisma);
144
+ }
145
+
146
+ console.log("✅ Database seeding completed");
147
+ }
148
+
149
+ main().catch((error) => {
150
+ console.error("❌ Seed failed:", error);
151
+ process.exit(1);
152
+ });
@@ -0,0 +1,27 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { defineConfig } from "prisma/config";
4
+ import { getConnectionString } from "./src/connection.js";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+
8
+ // Load .env from monorepo root (needed for standalone commands like db:push, db:seed)
9
+ try {
10
+ process.loadEnvFile(resolve(__dirname, "../../.env"));
11
+ } catch {}
12
+
13
+ // Use shared connection builder - throwOnMissing=false so `prisma generate` works
14
+ // even without database credentials (e.g. in CI). Commands that need a real
15
+ // connection (migrate, push, studio) will fail at connect time.
16
+ const databaseUrl = getConnectionString({ throwOnMissing: false });
17
+
18
+ export default defineConfig({
19
+ schema: "prisma/schema.prisma",
20
+ migrations: {
21
+ path: "prisma/migrations",
22
+ seed: "tsx prisma/seed.js",
23
+ },
24
+ datasource: {
25
+ url: databaseUrl,
26
+ },
27
+ });
@@ -0,0 +1,75 @@
1
+ import { PrismaPg } from "@prisma/adapter-pg";
2
+ import { getConnectionString } from "./connection.js";
3
+ import { PrismaClient } from "./generated/prisma/client.ts";
4
+
5
+ /**
6
+ * Returns the connection pool configuration for the `pg` driver.
7
+ *
8
+ * Reads from environment variables with sensible defaults:
9
+ * - `DB_POOL_MAX` - maximum number of connections (default: 10 in production, 5 in development)
10
+ * - `DB_POOL_IDLE_TIMEOUT` - milliseconds before an idle connection is closed (default: 30 000)
11
+ * - `DB_POOL_CONNECTION_TIMEOUT` - milliseconds to wait for a new connection (default: 5 000)
12
+ *
13
+ * @returns {{ max: number, idleTimeoutMillis: number, connectionTimeoutMillis: number }}
14
+ */
15
+ export function getPoolConfig() {
16
+ const isProduction = process.env.NODE_ENV === "production";
17
+ return {
18
+ max: Number(process.env.DB_POOL_MAX) || (isProduction ? 10 : 5),
19
+ idleTimeoutMillis: Number(process.env.DB_POOL_IDLE_TIMEOUT) || 30_000,
20
+ connectionTimeoutMillis:
21
+ Number(process.env.DB_POOL_CONNECTION_TIMEOUT) || 5_000,
22
+ };
23
+ }
24
+
25
+ // Lazy singleton - the client is created on first property access, not at import time.
26
+ // This allows Next.js to import the module at build time without requiring DB env vars.
27
+ const globalForPrisma = globalThis;
28
+
29
+ function getPrismaClient() {
30
+ if (!globalForPrisma.__prisma) {
31
+ const baseClient = new PrismaClient({
32
+ adapter: new PrismaPg({
33
+ connectionString: getConnectionString(),
34
+ ...getPoolConfig(),
35
+ }),
36
+ });
37
+
38
+ // Assign base client immediately so the proxy never blocks.
39
+ globalForPrisma.__prisma = baseClient;
40
+
41
+ // Apply optional query instrumentation extension from @techstream/quark-core.
42
+ // Loaded asynchronously — the base client is used in the meantime, then
43
+ // upgraded in-place once the extension module arrives.
44
+ // The extension handles slow-query logging, argument masking, and Prometheus
45
+ // metrics. It is schema-agnostic and respects the DB_INSTRUMENTATION env var.
46
+ import("@techstream/quark-core")
47
+ .then(({ createDbInstrumentation }) => {
48
+ const ext = createDbInstrumentation();
49
+ if (ext) {
50
+ globalForPrisma.__prisma = baseClient.$extends(ext);
51
+ }
52
+ })
53
+ .catch(() => {
54
+ // @techstream/quark-core not available or old version — base client remains.
55
+ });
56
+ }
57
+ return globalForPrisma.__prisma;
58
+ }
59
+
60
+ /**
61
+ * Prisma client singleton. Lazily initialized on first use so the module
62
+ * can be imported safely at build time (no DB env vars needed).
63
+ *
64
+ * Methods are bound to the real client so `this` inside Prisma internals is
65
+ * always the PrismaClient instance, never the Proxy wrapper.
66
+ */
67
+ export const prisma = new Proxy(/** @type {PrismaClient} */ ({}), {
68
+ get(_target, prop) {
69
+ const client = getPrismaClient();
70
+ const value = client[prop];
71
+ return typeof value === "function" ? value.bind(client) : value;
72
+ },
73
+ });
74
+
75
+ export * from "./generated/prisma/client.ts";