create-blitzpack 0.1.0 → 0.1.1

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 (259) hide show
  1. package/dist/index.js +35 -77
  2. package/package.json +5 -6
  3. package/template/.dockerignore +0 -59
  4. package/template/.github/workflows/ci.yml +0 -157
  5. package/template/.husky/pre-commit +0 -1
  6. package/template/.husky/pre-push +0 -1
  7. package/template/.lintstagedrc.cjs +0 -4
  8. package/template/.nvmrc +0 -1
  9. package/template/.prettierrc +0 -9
  10. package/template/.vscode/settings.json +0 -13
  11. package/template/CLAUDE.md +0 -175
  12. package/template/CONTRIBUTING.md +0 -32
  13. package/template/Dockerfile +0 -90
  14. package/template/GETTING_STARTED.md +0 -35
  15. package/template/LICENSE +0 -21
  16. package/template/README.md +0 -116
  17. package/template/apps/api/.dockerignore +0 -51
  18. package/template/apps/api/.env.local.example +0 -62
  19. package/template/apps/api/emails/account-deleted-email.tsx +0 -69
  20. package/template/apps/api/emails/components/email-layout.tsx +0 -154
  21. package/template/apps/api/emails/config.ts +0 -22
  22. package/template/apps/api/emails/password-changed-email.tsx +0 -88
  23. package/template/apps/api/emails/password-reset-email.tsx +0 -86
  24. package/template/apps/api/emails/verification-email.tsx +0 -85
  25. package/template/apps/api/emails/welcome-email.tsx +0 -70
  26. package/template/apps/api/package.json +0 -84
  27. package/template/apps/api/prisma/migrations/20251012111439_init/migration.sql +0 -13
  28. package/template/apps/api/prisma/migrations/20251018162629_add_better_auth_fields/migration.sql +0 -67
  29. package/template/apps/api/prisma/migrations/20251019142208_add_user_role_enum/migration.sql +0 -5
  30. package/template/apps/api/prisma/migrations/20251019182151_user_auth/migration.sql +0 -7
  31. package/template/apps/api/prisma/migrations/20251019211416_faster_session_lookup/migration.sql +0 -2
  32. package/template/apps/api/prisma/migrations/20251119124337_add_upload_model/migration.sql +0 -26
  33. package/template/apps/api/prisma/migrations/20251120071241_add_scope_to_account/migration.sql +0 -2
  34. package/template/apps/api/prisma/migrations/20251120072608_add_oauth_token_expiration_fields/migration.sql +0 -10
  35. package/template/apps/api/prisma/migrations/20251120144705_add_audit_logs/migration.sql +0 -29
  36. package/template/apps/api/prisma/migrations/20251127123614_remove_impersonated_by/migration.sql +0 -8
  37. package/template/apps/api/prisma/migrations/20251127125630_remove_audit_logs/migration.sql +0 -11
  38. package/template/apps/api/prisma/migrations/migration_lock.toml +0 -3
  39. package/template/apps/api/prisma/schema.prisma +0 -116
  40. package/template/apps/api/prisma/seed.ts +0 -159
  41. package/template/apps/api/prisma.config.ts +0 -14
  42. package/template/apps/api/src/app.ts +0 -377
  43. package/template/apps/api/src/common/logger.service.ts +0 -227
  44. package/template/apps/api/src/config/env.ts +0 -60
  45. package/template/apps/api/src/config/rate-limit.ts +0 -29
  46. package/template/apps/api/src/hooks/auth.ts +0 -122
  47. package/template/apps/api/src/plugins/auth.ts +0 -198
  48. package/template/apps/api/src/plugins/database.ts +0 -45
  49. package/template/apps/api/src/plugins/logger.ts +0 -33
  50. package/template/apps/api/src/plugins/multipart.ts +0 -16
  51. package/template/apps/api/src/plugins/scalar.ts +0 -20
  52. package/template/apps/api/src/plugins/schedule.ts +0 -52
  53. package/template/apps/api/src/plugins/services.ts +0 -66
  54. package/template/apps/api/src/plugins/swagger.ts +0 -56
  55. package/template/apps/api/src/routes/accounts.ts +0 -91
  56. package/template/apps/api/src/routes/admin-sessions.ts +0 -92
  57. package/template/apps/api/src/routes/metrics.ts +0 -71
  58. package/template/apps/api/src/routes/password.ts +0 -46
  59. package/template/apps/api/src/routes/sessions.ts +0 -53
  60. package/template/apps/api/src/routes/stats.ts +0 -38
  61. package/template/apps/api/src/routes/uploads-serve.ts +0 -27
  62. package/template/apps/api/src/routes/uploads.ts +0 -154
  63. package/template/apps/api/src/routes/users.ts +0 -114
  64. package/template/apps/api/src/routes/verification.ts +0 -90
  65. package/template/apps/api/src/server.ts +0 -34
  66. package/template/apps/api/src/services/accounts.service.ts +0 -125
  67. package/template/apps/api/src/services/authorization.service.ts +0 -162
  68. package/template/apps/api/src/services/email.service.ts +0 -170
  69. package/template/apps/api/src/services/file-storage.service.ts +0 -267
  70. package/template/apps/api/src/services/metrics.service.ts +0 -175
  71. package/template/apps/api/src/services/password.service.ts +0 -56
  72. package/template/apps/api/src/services/sessions.service.spec.ts +0 -134
  73. package/template/apps/api/src/services/sessions.service.ts +0 -276
  74. package/template/apps/api/src/services/stats.service.ts +0 -273
  75. package/template/apps/api/src/services/uploads.service.ts +0 -163
  76. package/template/apps/api/src/services/users.service.spec.ts +0 -249
  77. package/template/apps/api/src/services/users.service.ts +0 -198
  78. package/template/apps/api/src/utils/file-validation.ts +0 -108
  79. package/template/apps/api/start.sh +0 -33
  80. package/template/apps/api/test/helpers/fastify-app.ts +0 -24
  81. package/template/apps/api/test/helpers/mock-authorization.ts +0 -16
  82. package/template/apps/api/test/helpers/mock-logger.ts +0 -28
  83. package/template/apps/api/test/helpers/mock-prisma.ts +0 -30
  84. package/template/apps/api/test/helpers/test-db.ts +0 -125
  85. package/template/apps/api/test/integration/auth-flow.integration.spec.ts +0 -449
  86. package/template/apps/api/test/integration/password.integration.spec.ts +0 -427
  87. package/template/apps/api/test/integration/rate-limit.integration.spec.ts +0 -51
  88. package/template/apps/api/test/integration/sessions.integration.spec.ts +0 -445
  89. package/template/apps/api/test/integration/users.integration.spec.ts +0 -211
  90. package/template/apps/api/test/setup.ts +0 -31
  91. package/template/apps/api/tsconfig.json +0 -26
  92. package/template/apps/api/vitest.config.ts +0 -35
  93. package/template/apps/web/.env.local.example +0 -11
  94. package/template/apps/web/components.json +0 -24
  95. package/template/apps/web/next.config.ts +0 -22
  96. package/template/apps/web/package.json +0 -56
  97. package/template/apps/web/postcss.config.js +0 -5
  98. package/template/apps/web/public/apple-icon.png +0 -0
  99. package/template/apps/web/public/icon.png +0 -0
  100. package/template/apps/web/public/robots.txt +0 -3
  101. package/template/apps/web/src/app/(admin)/admin/layout.tsx +0 -222
  102. package/template/apps/web/src/app/(admin)/admin/page.tsx +0 -157
  103. package/template/apps/web/src/app/(admin)/admin/sessions/page.tsx +0 -18
  104. package/template/apps/web/src/app/(admin)/admin/users/page.tsx +0 -20
  105. package/template/apps/web/src/app/(auth)/forgot-password/page.tsx +0 -177
  106. package/template/apps/web/src/app/(auth)/login/page.tsx +0 -159
  107. package/template/apps/web/src/app/(auth)/reset-password/page.tsx +0 -245
  108. package/template/apps/web/src/app/(auth)/signup/page.tsx +0 -153
  109. package/template/apps/web/src/app/dashboard/change-password/page.tsx +0 -255
  110. package/template/apps/web/src/app/dashboard/page.tsx +0 -296
  111. package/template/apps/web/src/app/error.tsx +0 -32
  112. package/template/apps/web/src/app/examples/file-upload/page.tsx +0 -200
  113. package/template/apps/web/src/app/favicon.ico +0 -0
  114. package/template/apps/web/src/app/global-error.tsx +0 -96
  115. package/template/apps/web/src/app/globals.css +0 -22
  116. package/template/apps/web/src/app/icon.png +0 -0
  117. package/template/apps/web/src/app/layout.tsx +0 -34
  118. package/template/apps/web/src/app/not-found.tsx +0 -28
  119. package/template/apps/web/src/app/page.tsx +0 -192
  120. package/template/apps/web/src/components/admin/activity-feed.tsx +0 -101
  121. package/template/apps/web/src/components/admin/charts/auth-breakdown-chart.tsx +0 -114
  122. package/template/apps/web/src/components/admin/charts/chart-tooltip.tsx +0 -124
  123. package/template/apps/web/src/components/admin/charts/realtime-metrics-chart.tsx +0 -511
  124. package/template/apps/web/src/components/admin/charts/role-distribution-chart.tsx +0 -102
  125. package/template/apps/web/src/components/admin/charts/session-activity-chart.tsx +0 -90
  126. package/template/apps/web/src/components/admin/charts/user-growth-chart.tsx +0 -108
  127. package/template/apps/web/src/components/admin/health-indicator.tsx +0 -175
  128. package/template/apps/web/src/components/admin/refresh-control.tsx +0 -90
  129. package/template/apps/web/src/components/admin/session-revoke-all-dialog.tsx +0 -79
  130. package/template/apps/web/src/components/admin/session-revoke-dialog.tsx +0 -74
  131. package/template/apps/web/src/components/admin/sessions-management-table.tsx +0 -372
  132. package/template/apps/web/src/components/admin/stat-card.tsx +0 -137
  133. package/template/apps/web/src/components/admin/user-create-dialog.tsx +0 -152
  134. package/template/apps/web/src/components/admin/user-delete-dialog.tsx +0 -73
  135. package/template/apps/web/src/components/admin/user-edit-dialog.tsx +0 -170
  136. package/template/apps/web/src/components/admin/users-management-table.tsx +0 -285
  137. package/template/apps/web/src/components/auth/email-verification-banner.tsx +0 -85
  138. package/template/apps/web/src/components/auth/github-button.tsx +0 -40
  139. package/template/apps/web/src/components/auth/google-button.tsx +0 -54
  140. package/template/apps/web/src/components/auth/protected-route.tsx +0 -66
  141. package/template/apps/web/src/components/auth/redirect-if-authenticated.tsx +0 -31
  142. package/template/apps/web/src/components/auth/with-auth.tsx +0 -30
  143. package/template/apps/web/src/components/error/error-card.tsx +0 -47
  144. package/template/apps/web/src/components/error/forbidden.tsx +0 -25
  145. package/template/apps/web/src/components/landing/command-block.tsx +0 -64
  146. package/template/apps/web/src/components/landing/feature-card.tsx +0 -60
  147. package/template/apps/web/src/components/landing/included-feature-card.tsx +0 -63
  148. package/template/apps/web/src/components/landing/logo.tsx +0 -41
  149. package/template/apps/web/src/components/landing/tech-badge.tsx +0 -11
  150. package/template/apps/web/src/components/layout/auth-nav.tsx +0 -58
  151. package/template/apps/web/src/components/layout/footer.tsx +0 -3
  152. package/template/apps/web/src/config/landing-data.ts +0 -152
  153. package/template/apps/web/src/config/site.ts +0 -5
  154. package/template/apps/web/src/hooks/api/__tests__/use-users.test.tsx +0 -181
  155. package/template/apps/web/src/hooks/api/use-admin-sessions.ts +0 -75
  156. package/template/apps/web/src/hooks/api/use-admin-stats.ts +0 -33
  157. package/template/apps/web/src/hooks/api/use-sessions.ts +0 -52
  158. package/template/apps/web/src/hooks/api/use-uploads.ts +0 -156
  159. package/template/apps/web/src/hooks/api/use-users.ts +0 -149
  160. package/template/apps/web/src/hooks/use-mobile.ts +0 -21
  161. package/template/apps/web/src/hooks/use-realtime-metrics.ts +0 -120
  162. package/template/apps/web/src/lib/__tests__/utils.test.ts +0 -29
  163. package/template/apps/web/src/lib/api.ts +0 -151
  164. package/template/apps/web/src/lib/auth.ts +0 -13
  165. package/template/apps/web/src/lib/env.ts +0 -52
  166. package/template/apps/web/src/lib/form-utils.ts +0 -11
  167. package/template/apps/web/src/lib/utils.ts +0 -1
  168. package/template/apps/web/src/providers.tsx +0 -34
  169. package/template/apps/web/src/store/atoms.ts +0 -15
  170. package/template/apps/web/src/test/helpers/test-utils.tsx +0 -44
  171. package/template/apps/web/src/test/setup.ts +0 -8
  172. package/template/apps/web/tailwind.config.ts +0 -5
  173. package/template/apps/web/tsconfig.json +0 -26
  174. package/template/apps/web/vitest.config.ts +0 -32
  175. package/template/assets/logo-512.png +0 -0
  176. package/template/assets/logo.svg +0 -4
  177. package/template/docker-compose.prod.yml +0 -66
  178. package/template/docker-compose.yml +0 -36
  179. package/template/eslint.config.ts +0 -119
  180. package/template/package.json +0 -77
  181. package/template/packages/tailwind-config/package.json +0 -9
  182. package/template/packages/tailwind-config/theme.css +0 -179
  183. package/template/packages/types/package.json +0 -29
  184. package/template/packages/types/src/__tests__/schemas.test.ts +0 -255
  185. package/template/packages/types/src/api-response.ts +0 -53
  186. package/template/packages/types/src/health-check.ts +0 -11
  187. package/template/packages/types/src/pagination.ts +0 -41
  188. package/template/packages/types/src/role.ts +0 -5
  189. package/template/packages/types/src/session.ts +0 -48
  190. package/template/packages/types/src/stats.ts +0 -113
  191. package/template/packages/types/src/upload.ts +0 -51
  192. package/template/packages/types/src/user.ts +0 -36
  193. package/template/packages/types/tsconfig.json +0 -5
  194. package/template/packages/types/vitest.config.ts +0 -21
  195. package/template/packages/ui/components.json +0 -21
  196. package/template/packages/ui/package.json +0 -108
  197. package/template/packages/ui/src/__tests__/button.test.tsx +0 -70
  198. package/template/packages/ui/src/alert-dialog.tsx +0 -141
  199. package/template/packages/ui/src/alert.tsx +0 -66
  200. package/template/packages/ui/src/animated-theme-toggler.tsx +0 -167
  201. package/template/packages/ui/src/avatar.tsx +0 -53
  202. package/template/packages/ui/src/badge.tsx +0 -36
  203. package/template/packages/ui/src/button.tsx +0 -84
  204. package/template/packages/ui/src/card.tsx +0 -92
  205. package/template/packages/ui/src/checkbox.tsx +0 -32
  206. package/template/packages/ui/src/data-table/data-table-column-header.tsx +0 -68
  207. package/template/packages/ui/src/data-table/data-table-pagination.tsx +0 -99
  208. package/template/packages/ui/src/data-table/data-table-toolbar.tsx +0 -55
  209. package/template/packages/ui/src/data-table/data-table-view-options.tsx +0 -63
  210. package/template/packages/ui/src/data-table/data-table.tsx +0 -167
  211. package/template/packages/ui/src/dialog.tsx +0 -143
  212. package/template/packages/ui/src/dropdown-menu.tsx +0 -257
  213. package/template/packages/ui/src/empty-state.tsx +0 -52
  214. package/template/packages/ui/src/file-upload-input.tsx +0 -202
  215. package/template/packages/ui/src/form.tsx +0 -168
  216. package/template/packages/ui/src/hooks/use-mobile.ts +0 -19
  217. package/template/packages/ui/src/icons/brand-icons.tsx +0 -16
  218. package/template/packages/ui/src/input.tsx +0 -21
  219. package/template/packages/ui/src/label.tsx +0 -24
  220. package/template/packages/ui/src/lib/utils.ts +0 -6
  221. package/template/packages/ui/src/password-input.tsx +0 -102
  222. package/template/packages/ui/src/popover.tsx +0 -48
  223. package/template/packages/ui/src/radio-group.tsx +0 -45
  224. package/template/packages/ui/src/scroll-area.tsx +0 -58
  225. package/template/packages/ui/src/select.tsx +0 -187
  226. package/template/packages/ui/src/separator.tsx +0 -28
  227. package/template/packages/ui/src/sheet.tsx +0 -139
  228. package/template/packages/ui/src/sidebar.tsx +0 -726
  229. package/template/packages/ui/src/skeleton-variants.tsx +0 -87
  230. package/template/packages/ui/src/skeleton.tsx +0 -13
  231. package/template/packages/ui/src/slider.tsx +0 -63
  232. package/template/packages/ui/src/sonner.tsx +0 -25
  233. package/template/packages/ui/src/spinner.tsx +0 -16
  234. package/template/packages/ui/src/switch.tsx +0 -31
  235. package/template/packages/ui/src/table.tsx +0 -116
  236. package/template/packages/ui/src/tabs.tsx +0 -66
  237. package/template/packages/ui/src/textarea.tsx +0 -18
  238. package/template/packages/ui/src/tooltip.tsx +0 -61
  239. package/template/packages/ui/src/user-avatar.tsx +0 -97
  240. package/template/packages/ui/test-config.js +0 -3
  241. package/template/packages/ui/tsconfig.json +0 -12
  242. package/template/packages/ui/turbo.json +0 -18
  243. package/template/packages/ui/vitest.config.ts +0 -17
  244. package/template/packages/ui/vitest.setup.ts +0 -1
  245. package/template/packages/utils/package.json +0 -23
  246. package/template/packages/utils/src/__tests__/utils.test.ts +0 -223
  247. package/template/packages/utils/src/array.ts +0 -18
  248. package/template/packages/utils/src/async.ts +0 -3
  249. package/template/packages/utils/src/date.ts +0 -77
  250. package/template/packages/utils/src/errors.ts +0 -73
  251. package/template/packages/utils/src/number.ts +0 -11
  252. package/template/packages/utils/src/string.ts +0 -13
  253. package/template/packages/utils/tsconfig.json +0 -5
  254. package/template/packages/utils/vitest.config.ts +0 -21
  255. package/template/pnpm-workspace.yaml +0 -4
  256. package/template/tsconfig.base.json +0 -32
  257. package/template/turbo.json +0 -133
  258. package/template/vitest.shared.ts +0 -26
  259. package/template/vitest.workspace.ts +0 -9
@@ -1,33 +0,0 @@
1
- #!/bin/sh
2
- set -e
3
-
4
- echo "🚀 Starting API deployment..."
5
-
6
- # Wait for database to be ready (handles Railway sleep mode)
7
- echo "⏳ Waiting for database to wake up..."
8
-
9
- max_attempts=30
10
- attempt=0
11
-
12
- until npx prisma db push --accept-data-loss --skip-generate || [ $attempt -eq $max_attempts ]; do
13
- attempt=$((attempt + 1))
14
- echo " Database not ready yet (attempt $attempt/$max_attempts). Retrying in 2 seconds..."
15
- sleep 2
16
- done
17
-
18
- if [ $attempt -eq $max_attempts ]; then
19
- echo "❌ Database connection failed after $max_attempts attempts"
20
- exit 1
21
- fi
22
-
23
- echo "✅ Database is ready!"
24
-
25
- # Run migrations (production-safe, no prompts)
26
- echo "🔄 Running database migrations..."
27
- npx prisma migrate deploy
28
-
29
- echo "✅ Migrations complete!"
30
-
31
- # Start the API server
32
- echo "🎯 Starting API server..."
33
- exec node dist/src/main.js
@@ -1,24 +0,0 @@
1
- import type { FastifyInstance } from 'fastify';
2
- import Fastify from 'fastify';
3
-
4
- import { loadEnv } from '@/config/env';
5
-
6
- /**
7
- * Create a Fastify test instance with minimal plugins for testing
8
- */
9
- export async function createTestApp(): Promise<FastifyInstance> {
10
- loadEnv(); // Load test environment variables
11
-
12
- const app = Fastify({
13
- logger: false, // Disable logging in tests
14
- });
15
-
16
- return app;
17
- }
18
-
19
- /**
20
- * Close the Fastify test instance
21
- */
22
- export async function closeTestApp(app: FastifyInstance): Promise<void> {
23
- await app.close();
24
- }
@@ -1,16 +0,0 @@
1
- import { vi } from 'vitest';
2
-
3
- import type { AuthorizationService } from '@/services/authorization.service';
4
-
5
- export function createMockAuthorizationService(): AuthorizationService {
6
- return {
7
- canModifyUser: vi.fn().mockReturnValue(true),
8
- canDeleteUser: vi.fn().mockReturnValue(true),
9
- canChangeRole: vi.fn().mockReturnValue(true),
10
- canChangeEmail: vi.fn().mockReturnValue(true),
11
- assertCanModifyUser: vi.fn(),
12
- assertCanDeleteUser: vi.fn(),
13
- assertCanChangeRole: vi.fn(),
14
- assertCanChangeEmail: vi.fn(),
15
- } as unknown as AuthorizationService;
16
- }
@@ -1,28 +0,0 @@
1
- import { vi } from 'vitest';
2
-
3
- import type { LoggerService } from '@/common/logger.service';
4
-
5
- export function createMockLogger(): LoggerService {
6
- const mockLogger = {
7
- setContext: vi.fn(),
8
- info: vi.fn(),
9
- error: vi.fn(),
10
- warn: vi.fn(),
11
- debug: vi.fn(),
12
- http: vi.fn(),
13
- child: vi.fn(),
14
- minimal: vi.fn(),
15
- normal: vi.fn(),
16
- detailed: vi.fn(),
17
- verbose: vi.fn(),
18
- } as unknown as LoggerService;
19
-
20
- // Make verbosity methods chainable
21
- mockLogger.minimal = vi.fn().mockReturnValue(mockLogger);
22
- mockLogger.normal = vi.fn().mockReturnValue(mockLogger);
23
- mockLogger.detailed = vi.fn().mockReturnValue(mockLogger);
24
- mockLogger.verbose = vi.fn().mockReturnValue(mockLogger);
25
- mockLogger.child = vi.fn().mockReturnValue(mockLogger);
26
-
27
- return mockLogger;
28
- }
@@ -1,30 +0,0 @@
1
- import { vi } from 'vitest';
2
-
3
- import type { PrismaClient } from '@/generated/client/client.js';
4
-
5
- export function createMockPrisma(): PrismaClient {
6
- return {
7
- user: {
8
- findMany: vi.fn(),
9
- findUnique: vi.fn(),
10
- findFirst: vi.fn(),
11
- create: vi.fn(),
12
- update: vi.fn(),
13
- delete: vi.fn(),
14
- count: vi.fn(),
15
- },
16
- session: {
17
- findMany: vi.fn(),
18
- findUnique: vi.fn(),
19
- findFirst: vi.fn(),
20
- create: vi.fn(),
21
- update: vi.fn(),
22
- delete: vi.fn(),
23
- deleteMany: vi.fn(),
24
- count: vi.fn(),
25
- },
26
- $connect: vi.fn(),
27
- $disconnect: vi.fn(),
28
- $queryRaw: vi.fn(),
29
- } as unknown as PrismaClient;
30
- }
@@ -1,125 +0,0 @@
1
- import 'dotenv-flow/config';
2
-
3
- import { PrismaPg } from '@prisma/adapter-pg';
4
- import { execSync } from 'child_process';
5
- import { Pool } from 'pg';
6
-
7
- import { PrismaClient } from '@/generated/client/client.js';
8
-
9
- let prisma: PrismaClient | null = null;
10
- let pool: Pool | null = null;
11
-
12
- /**
13
- * Get test database URL
14
- * Uses DATABASE_URL from env but appends _test suffix to database name
15
- */
16
- function getTestDatabaseUrl(): string {
17
- const databaseUrl =
18
- process.env.DATABASE_URL ||
19
- 'postgresql://postgres:postgres_dev_password@localhost:5432/app_dev';
20
-
21
- const url = new URL(databaseUrl);
22
- const dbName = url.pathname.slice(1);
23
- url.pathname = `/${dbName}_test`;
24
-
25
- return url.toString();
26
- }
27
-
28
- /**
29
- * Setup test database before running tests
30
- * - Sets up test database URL
31
- * - Runs migrations
32
- * - Creates Prisma client instance
33
- */
34
- export async function setupTestDatabase(): Promise<void> {
35
- const testDatabaseUrl = getTestDatabaseUrl();
36
-
37
- process.env.DATABASE_URL = testDatabaseUrl;
38
-
39
- try {
40
- // Run migrations on test database
41
- console.log('⏳ Running test database migrations...');
42
- execSync('npx prisma migrate deploy', {
43
- stdio: 'inherit',
44
- env: { ...process.env, DATABASE_URL: testDatabaseUrl },
45
- });
46
-
47
- pool = new Pool({ connectionString: testDatabaseUrl });
48
- const adapter = new PrismaPg(pool);
49
- prisma = new PrismaClient({ adapter });
50
-
51
- await prisma.$connect();
52
- console.log('✅ Test database ready');
53
- } catch (error) {
54
- console.error('❌ Failed to setup test database:', error);
55
- throw error;
56
- }
57
- }
58
-
59
- /**
60
- * Cleanup test database after tests complete
61
- * - Disconnects Prisma client
62
- * - Optionally drops test database (set KEEP_TEST_DB=true to preserve)
63
- */
64
- export async function cleanupTestDatabase(): Promise<void> {
65
- if (prisma) {
66
- await prisma.$disconnect();
67
- prisma = null;
68
- }
69
-
70
- if (pool) {
71
- await pool.end();
72
- pool = null;
73
- }
74
-
75
- if (process.env.KEEP_TEST_DB !== 'true') {
76
- try {
77
- const testDatabaseUrl = getTestDatabaseUrl();
78
- const url = new URL(testDatabaseUrl);
79
- const dbName = url.pathname.slice(1);
80
-
81
- url.pathname = '/postgres';
82
- const adminUrl = url.toString();
83
-
84
- console.log('⏳ Cleaning up test database...');
85
-
86
- const adminPool = new Pool({ connectionString: adminUrl });
87
- const adminAdapter = new PrismaPg(adminPool);
88
- const adminPrisma = new PrismaClient({ adapter: adminAdapter });
89
-
90
- await adminPrisma.$connect();
91
- await adminPrisma.$executeRawUnsafe(
92
- `DROP DATABASE IF EXISTS "${dbName}" WITH (FORCE);`
93
- );
94
- await adminPrisma.$disconnect();
95
- await adminPool.end();
96
-
97
- console.log('✅ Test database cleaned up');
98
- } catch (error) {
99
- console.warn('⚠️ Test database cleanup failed (usually okay)');
100
- }
101
- }
102
- }
103
-
104
- export function getTestPrisma(): PrismaClient {
105
- if (!prisma) {
106
- throw new Error(
107
- 'Test database not initialized. Make sure setupTestDatabase() was called.'
108
- );
109
- }
110
- return prisma;
111
- }
112
-
113
- /**
114
- * Reset test database between tests
115
- * Truncates all tables while preserving schema
116
- */
117
- export async function resetTestDatabase(): Promise<void> {
118
- const client = getTestPrisma();
119
-
120
- // Disable foreign key checks and truncate all tables at once
121
- // This is more efficient and avoids deadlocks
122
- await client.$executeRaw`
123
- TRUNCATE TABLE "verifications", "sessions", "accounts", "users" RESTART IDENTITY CASCADE;
124
- `;
125
- }
@@ -1,449 +0,0 @@
1
- import { getTestPrisma, resetTestDatabase } from '@test/helpers/test-db';
2
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3
-
4
- describe('Authentication Flow Integration Tests', () => {
5
- beforeEach(async () => {
6
- await resetTestDatabase();
7
- });
8
-
9
- afterEach(async () => {
10
- // Additional cleanup if needed
11
- });
12
-
13
- describe('Signup and Login Flow', () => {
14
- it('should allow user to signup and login with email/password', async () => {
15
- const prisma = getTestPrisma();
16
-
17
- const email = 'newuser@test.com';
18
- const password = 'SecurePass123!';
19
- const name = 'New User';
20
-
21
- // Simulate signup by creating user directly in database
22
- // (Better Auth handles signup via API endpoint)
23
- const user = await prisma.user.create({
24
- data: {
25
- email,
26
- name,
27
- emailVerified: false,
28
- },
29
- });
30
-
31
- // Create credential account for password auth
32
- const bcrypt = await import('bcryptjs');
33
- const hashedPassword = await bcrypt.hash(password, 10);
34
-
35
- await prisma.account.create({
36
- data: {
37
- userId: user.id,
38
- accountId: email, // Use email as accountId for credential provider
39
- providerId: 'credential',
40
- password: hashedPassword,
41
- },
42
- });
43
-
44
- // Verify user was created
45
- const createdUser = await prisma.user.findUnique({
46
- where: { email },
47
- });
48
-
49
- expect(createdUser).toBeDefined();
50
- expect(createdUser?.email).toBe(email);
51
- expect(createdUser?.name).toBe(name);
52
- expect(createdUser?.emailVerified).toBe(false);
53
- });
54
-
55
- it('should create session on successful login', async () => {
56
- const prisma = getTestPrisma();
57
-
58
- const user = await prisma.user.create({
59
- data: {
60
- email: 'session@test.com',
61
- name: 'Session User',
62
- emailVerified: true,
63
- },
64
- });
65
-
66
- const bcrypt = await import('bcryptjs');
67
- const hashedPassword = await bcrypt.hash('password123', 10);
68
-
69
- await prisma.account.create({
70
- data: {
71
- userId: user.id,
72
- accountId: 'session@test.com', // Use email as accountId for credential provider
73
- providerId: 'credential',
74
- password: hashedPassword,
75
- },
76
- });
77
-
78
- // Create session manually (Better Auth would do this on login)
79
- const session = await prisma.session.create({
80
- data: {
81
- userId: user.id,
82
- token: 'test-session-token',
83
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
84
- ipAddress: '127.0.0.1',
85
- userAgent: 'Test Browser',
86
- },
87
- });
88
-
89
- expect(session).toBeDefined();
90
- expect(session.userId).toBe(user.id);
91
- expect(session.expiresAt.getTime()).toBeGreaterThan(Date.now());
92
- });
93
-
94
- it('should not allow duplicate email signups', async () => {
95
- const prisma = getTestPrisma();
96
-
97
- const email = 'duplicate@test.com';
98
-
99
- // Create first user
100
- await prisma.user.create({
101
- data: {
102
- email,
103
- name: 'First User',
104
- },
105
- });
106
-
107
- // Attempt to create second user with same email
108
- await expect(
109
- prisma.user.create({
110
- data: {
111
- email,
112
- name: 'Second User',
113
- },
114
- })
115
- ).rejects.toThrow();
116
- });
117
- });
118
-
119
- describe('Session Management', () => {
120
- it('should allow multiple sessions for same user', async () => {
121
- const prisma = getTestPrisma();
122
-
123
- const user = await prisma.user.create({
124
- data: {
125
- email: 'multisession@test.com',
126
- name: 'Multi Session User',
127
- },
128
- });
129
-
130
- // Create multiple sessions (different devices)
131
- const session1 = await prisma.session.create({
132
- data: {
133
- userId: user.id,
134
- token: 'session-1-token',
135
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
136
- ipAddress: '192.168.1.1',
137
- userAgent: 'Desktop Browser',
138
- },
139
- });
140
-
141
- const session2 = await prisma.session.create({
142
- data: {
143
- userId: user.id,
144
- token: 'session-2-token',
145
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
146
- ipAddress: '192.168.1.2',
147
- userAgent: 'Mobile Browser',
148
- },
149
- });
150
-
151
- const sessions = await prisma.session.findMany({
152
- where: { userId: user.id },
153
- });
154
-
155
- expect(sessions).toHaveLength(2);
156
- expect(sessions[0].id).toBe(session1.id);
157
- expect(sessions[1].id).toBe(session2.id);
158
- });
159
-
160
- it('should filter out expired sessions', async () => {
161
- const prisma = getTestPrisma();
162
-
163
- const user = await prisma.user.create({
164
- data: {
165
- email: 'expired@test.com',
166
- name: 'Expired Session User',
167
- },
168
- });
169
-
170
- // Create active session
171
- await prisma.session.create({
172
- data: {
173
- userId: user.id,
174
- token: 'active-session',
175
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
176
- },
177
- });
178
-
179
- // Create expired session
180
- await prisma.session.create({
181
- data: {
182
- userId: user.id,
183
- token: 'expired-session',
184
- expiresAt: new Date(Date.now() - 1000), // Already expired
185
- },
186
- });
187
-
188
- // Query only active sessions
189
- const activeSessions = await prisma.session.findMany({
190
- where: {
191
- userId: user.id,
192
- expiresAt: {
193
- gt: new Date(),
194
- },
195
- },
196
- });
197
-
198
- expect(activeSessions).toHaveLength(1);
199
- expect(activeSessions[0].token).toBe('active-session');
200
- });
201
-
202
- it('should delete session on logout', async () => {
203
- const prisma = getTestPrisma();
204
-
205
- const user = await prisma.user.create({
206
- data: {
207
- email: 'logout@test.com',
208
- name: 'Logout User',
209
- },
210
- });
211
-
212
- const session = await prisma.session.create({
213
- data: {
214
- userId: user.id,
215
- token: 'logout-session',
216
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
217
- },
218
- });
219
-
220
- // Delete session (logout)
221
- await prisma.session.delete({
222
- where: { id: session.id },
223
- });
224
-
225
- const deletedSession = await prisma.session.findUnique({
226
- where: { id: session.id },
227
- });
228
-
229
- expect(deletedSession).toBeNull();
230
- });
231
-
232
- it('should revoke all user sessions', async () => {
233
- const prisma = getTestPrisma();
234
-
235
- const user = await prisma.user.create({
236
- data: {
237
- email: 'revoke-all@test.com',
238
- name: 'Revoke All User',
239
- },
240
- });
241
-
242
- // Create multiple sessions
243
- await prisma.session.createMany({
244
- data: [
245
- {
246
- userId: user.id,
247
- token: 'session-1',
248
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
249
- },
250
- {
251
- userId: user.id,
252
- token: 'session-2',
253
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
254
- },
255
- {
256
- userId: user.id,
257
- token: 'session-3',
258
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
259
- },
260
- ],
261
- });
262
-
263
- // Revoke all sessions
264
- await prisma.session.deleteMany({
265
- where: { userId: user.id },
266
- });
267
-
268
- const remainingSessions = await prisma.session.findMany({
269
- where: { userId: user.id },
270
- });
271
-
272
- expect(remainingSessions).toHaveLength(0);
273
- });
274
- });
275
-
276
- describe('Email Verification Flow', () => {
277
- it('should create verification record on signup', async () => {
278
- const prisma = getTestPrisma();
279
-
280
- const user = await prisma.user.create({
281
- data: {
282
- email: 'verify@test.com',
283
- name: 'Verify User',
284
- emailVerified: false,
285
- },
286
- });
287
-
288
- // Create verification token
289
- const verification = await prisma.verification.create({
290
- data: {
291
- identifier: user.email,
292
- value: 'verification-token-12345',
293
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
294
- },
295
- });
296
-
297
- expect(verification).toBeDefined();
298
- expect(verification.identifier).toBe(user.email);
299
- expect(verification.expiresAt.getTime()).toBeGreaterThan(Date.now());
300
- });
301
-
302
- it('should mark user as verified after email verification', async () => {
303
- const prisma = getTestPrisma();
304
-
305
- const user = await prisma.user.create({
306
- data: {
307
- email: 'verify-success@test.com',
308
- name: 'Verify Success User',
309
- emailVerified: false,
310
- },
311
- });
312
-
313
- // Simulate verification
314
- const updatedUser = await prisma.user.update({
315
- where: { id: user.id },
316
- data: { emailVerified: true },
317
- });
318
-
319
- expect(updatedUser.emailVerified).toBe(true);
320
- });
321
-
322
- it('should delete verification token after successful verification', async () => {
323
- const prisma = getTestPrisma();
324
-
325
- const verification = await prisma.verification.create({
326
- data: {
327
- identifier: 'delete-token@test.com',
328
- value: 'token-to-delete',
329
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
330
- },
331
- });
332
-
333
- // Delete verification token after use
334
- await prisma.verification.delete({
335
- where: { id: verification.id },
336
- });
337
-
338
- const deletedVerification = await prisma.verification.findUnique({
339
- where: { id: verification.id },
340
- });
341
-
342
- expect(deletedVerification).toBeNull();
343
- });
344
- });
345
-
346
- describe('Password Reset Flow', () => {
347
- it('should create password reset token', async () => {
348
- const prisma = getTestPrisma();
349
-
350
- const user = await prisma.user.create({
351
- data: {
352
- email: 'reset@test.com',
353
- name: 'Reset User',
354
- },
355
- });
356
-
357
- // Create password reset verification
358
- const resetToken = await prisma.verification.create({
359
- data: {
360
- identifier: user.email,
361
- value: 'reset-token-12345',
362
- expiresAt: new Date(Date.now() + 3600 * 1000), // 1 hour
363
- },
364
- });
365
-
366
- expect(resetToken).toBeDefined();
367
- expect(resetToken.identifier).toBe(user.email);
368
- });
369
-
370
- it('should update password and invalidate reset token', async () => {
371
- const prisma = getTestPrisma();
372
-
373
- const user = await prisma.user.create({
374
- data: {
375
- email: 'password-change@test.com',
376
- name: 'Password Change User',
377
- },
378
- });
379
-
380
- // Create account with old password
381
- const bcrypt = await import('bcryptjs');
382
- const oldPassword = await bcrypt.hash('oldPassword123', 10);
383
-
384
- const account = await prisma.account.create({
385
- data: {
386
- userId: user.id,
387
- accountId: user.id,
388
- providerId: 'credential',
389
- password: oldPassword,
390
- },
391
- });
392
-
393
- // Update password
394
- const newPassword = await bcrypt.hash('newPassword456', 10);
395
- const updatedAccount = await prisma.account.update({
396
- where: { id: account.id },
397
- data: { password: newPassword },
398
- });
399
-
400
- // Verify password was changed
401
- const isOldPasswordValid = await bcrypt.compare(
402
- 'oldPassword123',
403
- updatedAccount.password!
404
- );
405
- const isNewPasswordValid = await bcrypt.compare(
406
- 'newPassword456',
407
- updatedAccount.password!
408
- );
409
-
410
- expect(isOldPasswordValid).toBe(false);
411
- expect(isNewPasswordValid).toBe(true);
412
- });
413
- });
414
-
415
- describe('User Role Management', () => {
416
- it('should assign default role on user creation', async () => {
417
- const prisma = getTestPrisma();
418
-
419
- const user = await prisma.user.create({
420
- data: {
421
- email: 'default-role@test.com',
422
- name: 'Default Role User',
423
- },
424
- });
425
-
426
- // Better Auth admin plugin uses 'role' field
427
- // Default role should be 'user' (as configured in auth plugin)
428
- const userWithRole = user as typeof user & { role?: string };
429
- expect(userWithRole.role || 'user').toBe('user');
430
- });
431
-
432
- it('should allow admin role assignment', async () => {
433
- const prisma = getTestPrisma();
434
-
435
- // Create user with admin role
436
- const adminUser = await prisma.user.create({
437
- data: {
438
- email: 'admin@test.com',
439
- name: 'Admin User',
440
- // Note: Better Auth admin plugin manages roles via user.role field
441
- // The exact implementation depends on your Prisma schema
442
- },
443
- });
444
-
445
- expect(adminUser).toBeDefined();
446
- expect(adminUser.email).toBe('admin@test.com');
447
- });
448
- });
449
- });