create-blitzpack 0.1.0 → 0.1.2

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 +92 -94
  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,445 +0,0 @@
1
- import { NotFoundError } from '@repo/packages-utils/errors';
2
- import { createMockLogger } from '@test/helpers/mock-logger';
3
- import { getTestPrisma, resetTestDatabase } from '@test/helpers/test-db';
4
- import { beforeEach, describe, expect, it } from 'vitest';
5
-
6
- import type { LoggerService } from '@/common/logger.service';
7
- import { SessionsService } from '@/services/sessions.service';
8
-
9
- describe('Sessions Service Integration Tests', () => {
10
- let service: SessionsService;
11
- let logger: LoggerService;
12
-
13
- beforeEach(async () => {
14
- await resetTestDatabase();
15
-
16
- logger = createMockLogger();
17
- const prisma = getTestPrisma();
18
- service = new SessionsService(prisma);
19
- });
20
-
21
- describe('getUserSessions', () => {
22
- it('should return all active sessions for a user', async () => {
23
- const prisma = getTestPrisma();
24
-
25
- // Create test user
26
- const user = await prisma.user.create({
27
- data: {
28
- email: 'sessions@test.com',
29
- name: 'Sessions User',
30
- },
31
- });
32
-
33
- // Create multiple sessions
34
- await prisma.session.createMany({
35
- data: [
36
- {
37
- userId: user.id,
38
- token: 'session-1',
39
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
40
- ipAddress: '192.168.1.1',
41
- userAgent: 'Chrome Desktop',
42
- },
43
- {
44
- userId: user.id,
45
- token: 'session-2',
46
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
47
- ipAddress: '192.168.1.2',
48
- userAgent: 'Safari Mobile',
49
- },
50
- {
51
- userId: user.id,
52
- token: 'session-3',
53
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
54
- ipAddress: '10.0.0.1',
55
- userAgent: 'Firefox Desktop',
56
- },
57
- ],
58
- });
59
-
60
- const sessions = await service.getUserSessions(user.id);
61
-
62
- expect(sessions).toHaveLength(3);
63
- expect(sessions[0].ipAddress).toBeDefined();
64
- expect(sessions[0].userAgent).toBeDefined();
65
- expect(sessions[0].expiresAt).toBeInstanceOf(Date);
66
- });
67
-
68
- it('should exclude expired sessions', async () => {
69
- const prisma = getTestPrisma();
70
-
71
- const user = await prisma.user.create({
72
- data: {
73
- email: 'expired@test.com',
74
- name: 'Expired User',
75
- },
76
- });
77
-
78
- // Create active and expired sessions
79
- await prisma.session.createMany({
80
- data: [
81
- {
82
- userId: user.id,
83
- token: 'active-session',
84
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
85
- },
86
- {
87
- userId: user.id,
88
- token: 'expired-session-1',
89
- expiresAt: new Date(Date.now() - 1000),
90
- },
91
- {
92
- userId: user.id,
93
- token: 'expired-session-2',
94
- expiresAt: new Date(Date.now() - 5000),
95
- },
96
- ],
97
- });
98
-
99
- const sessions = await service.getUserSessions(user.id);
100
-
101
- expect(sessions).toHaveLength(1);
102
- expect(sessions[0].expiresAt.getTime()).toBeGreaterThan(Date.now());
103
- });
104
-
105
- it('should return sessions ordered by most recently updated', async () => {
106
- const prisma = getTestPrisma();
107
-
108
- const user = await prisma.user.create({
109
- data: {
110
- email: 'ordered@test.com',
111
- name: 'Ordered User',
112
- },
113
- });
114
-
115
- const now = Date.now();
116
-
117
- // Create sessions with different update times
118
- const session1 = await prisma.session.create({
119
- data: {
120
- userId: user.id,
121
- token: 'oldest',
122
- expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
123
- updatedAt: new Date(now - 3000),
124
- },
125
- });
126
-
127
- const session2 = await prisma.session.create({
128
- data: {
129
- userId: user.id,
130
- token: 'middle',
131
- expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
132
- updatedAt: new Date(now - 2000),
133
- },
134
- });
135
-
136
- const session3 = await prisma.session.create({
137
- data: {
138
- userId: user.id,
139
- token: 'newest',
140
- expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
141
- updatedAt: new Date(now - 1000),
142
- },
143
- });
144
-
145
- const sessions = await service.getUserSessions(user.id);
146
-
147
- expect(sessions).toHaveLength(3);
148
- expect(sessions[0].id).toBe(session3.id); // Newest first
149
- expect(sessions[1].id).toBe(session2.id);
150
- expect(sessions[2].id).toBe(session1.id);
151
- });
152
-
153
- it('should return empty array for user with no sessions', async () => {
154
- const prisma = getTestPrisma();
155
-
156
- const user = await prisma.user.create({
157
- data: {
158
- email: 'nosessions@test.com',
159
- name: 'No Sessions User',
160
- },
161
- });
162
-
163
- const sessions = await service.getUserSessions(user.id);
164
-
165
- expect(sessions).toHaveLength(0);
166
- });
167
- });
168
-
169
- describe('revokeSession', () => {
170
- it('should delete a specific session', async () => {
171
- const prisma = getTestPrisma();
172
-
173
- const user = await prisma.user.create({
174
- data: {
175
- email: 'revoke@test.com',
176
- name: 'Revoke User',
177
- },
178
- });
179
-
180
- const session = await prisma.session.create({
181
- data: {
182
- userId: user.id,
183
- token: 'to-revoke',
184
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
185
- },
186
- });
187
-
188
- await service.revokeSession(user.id, session.id);
189
-
190
- const deletedSession = await prisma.session.findUnique({
191
- where: { id: session.id },
192
- });
193
-
194
- expect(deletedSession).toBeNull();
195
- });
196
-
197
- it('should throw NotFoundError when session does not exist', async () => {
198
- const prisma = getTestPrisma();
199
-
200
- const user = await prisma.user.create({
201
- data: {
202
- email: 'notfound@test.com',
203
- name: 'Not Found User',
204
- },
205
- });
206
-
207
- await expect(
208
- service.revokeSession(user.id, 'non-existent-session-id')
209
- ).rejects.toThrow(NotFoundError);
210
- });
211
-
212
- it('should throw NotFoundError when session belongs to different user', async () => {
213
- const prisma = getTestPrisma();
214
-
215
- const user1 = await prisma.user.create({
216
- data: {
217
- email: 'user1@test.com',
218
- name: 'User 1',
219
- },
220
- });
221
-
222
- const user2 = await prisma.user.create({
223
- data: {
224
- email: 'user2@test.com',
225
- name: 'User 2',
226
- },
227
- });
228
-
229
- const user2Session = await prisma.session.create({
230
- data: {
231
- userId: user2.id,
232
- token: 'user2-session',
233
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
234
- },
235
- });
236
-
237
- // User1 tries to revoke User2's session
238
- await expect(
239
- service.revokeSession(user1.id, user2Session.id)
240
- ).rejects.toThrow(NotFoundError);
241
- });
242
-
243
- it('should only delete the specified session, not others', async () => {
244
- const prisma = getTestPrisma();
245
-
246
- const user = await prisma.user.create({
247
- data: {
248
- email: 'multiple@test.com',
249
- name: 'Multiple Sessions User',
250
- },
251
- });
252
-
253
- const session1 = await prisma.session.create({
254
- data: {
255
- userId: user.id,
256
- token: 'keep-this',
257
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
258
- },
259
- });
260
-
261
- const session2 = await prisma.session.create({
262
- data: {
263
- userId: user.id,
264
- token: 'delete-this',
265
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
266
- },
267
- });
268
-
269
- await service.revokeSession(user.id, session2.id);
270
-
271
- const remainingSessions = await prisma.session.findMany({
272
- where: { userId: user.id },
273
- });
274
-
275
- expect(remainingSessions).toHaveLength(1);
276
- expect(remainingSessions[0].id).toBe(session1.id);
277
- });
278
- });
279
-
280
- describe('revokeAllSessions', () => {
281
- it('should delete all sessions except current one', async () => {
282
- const prisma = getTestPrisma();
283
-
284
- const user = await prisma.user.create({
285
- data: {
286
- email: 'revokeall@test.com',
287
- name: 'Revoke All User',
288
- },
289
- });
290
-
291
- const currentSession = await prisma.session.create({
292
- data: {
293
- userId: user.id,
294
- token: 'current',
295
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
296
- },
297
- });
298
-
299
- await prisma.session.createMany({
300
- data: [
301
- {
302
- userId: user.id,
303
- token: 'other-1',
304
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
305
- },
306
- {
307
- userId: user.id,
308
- token: 'other-2',
309
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
310
- },
311
- ],
312
- });
313
-
314
- await service.revokeAllSessions(user.id, currentSession.id);
315
-
316
- const remainingSessions = await prisma.session.findMany({
317
- where: { userId: user.id },
318
- });
319
-
320
- expect(remainingSessions).toHaveLength(1);
321
- expect(remainingSessions[0].id).toBe(currentSession.id);
322
- });
323
-
324
- it('should delete all sessions when no current session provided', async () => {
325
- const prisma = getTestPrisma();
326
-
327
- const user = await prisma.user.create({
328
- data: {
329
- email: 'deleteall@test.com',
330
- name: 'Delete All User',
331
- },
332
- });
333
-
334
- await prisma.session.createMany({
335
- data: [
336
- {
337
- userId: user.id,
338
- token: 'session-1',
339
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
340
- },
341
- {
342
- userId: user.id,
343
- token: 'session-2',
344
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
345
- },
346
- {
347
- userId: user.id,
348
- token: 'session-3',
349
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
350
- },
351
- ],
352
- });
353
-
354
- await service.revokeAllSessions(user.id);
355
-
356
- const remainingSessions = await prisma.session.findMany({
357
- where: { userId: user.id },
358
- });
359
-
360
- expect(remainingSessions).toHaveLength(0);
361
- });
362
- });
363
-
364
- describe('revokeAllUserSessions', () => {
365
- it('should delete all sessions for a user', async () => {
366
- const prisma = getTestPrisma();
367
-
368
- const user = await prisma.user.create({
369
- data: {
370
- email: 'total-revoke@test.com',
371
- name: 'Total Revoke User',
372
- },
373
- });
374
-
375
- await prisma.session.createMany({
376
- data: [
377
- {
378
- userId: user.id,
379
- token: 'session-1',
380
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
381
- },
382
- {
383
- userId: user.id,
384
- token: 'session-2',
385
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
386
- },
387
- ],
388
- });
389
-
390
- await service.revokeAllUserSessions(user.id);
391
-
392
- const sessions = await prisma.session.findMany({
393
- where: { userId: user.id },
394
- });
395
-
396
- expect(sessions).toHaveLength(0);
397
- });
398
-
399
- it('should not affect other users sessions', async () => {
400
- const prisma = getTestPrisma();
401
-
402
- const user1 = await prisma.user.create({
403
- data: {
404
- email: 'user1-revoke@test.com',
405
- name: 'User 1',
406
- },
407
- });
408
-
409
- const user2 = await prisma.user.create({
410
- data: {
411
- email: 'user2-keep@test.com',
412
- name: 'User 2',
413
- },
414
- });
415
-
416
- await prisma.session.create({
417
- data: {
418
- userId: user1.id,
419
- token: 'user1-session',
420
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
421
- },
422
- });
423
-
424
- await prisma.session.create({
425
- data: {
426
- userId: user2.id,
427
- token: 'user2-session',
428
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
429
- },
430
- });
431
-
432
- await service.revokeAllUserSessions(user1.id);
433
-
434
- const user1Sessions = await prisma.session.findMany({
435
- where: { userId: user1.id },
436
- });
437
- const user2Sessions = await prisma.session.findMany({
438
- where: { userId: user2.id },
439
- });
440
-
441
- expect(user1Sessions).toHaveLength(0);
442
- expect(user2Sessions).toHaveLength(1);
443
- });
444
- });
445
- });
@@ -1,211 +0,0 @@
1
- import { createMockAuthorizationService } from '@test/helpers/mock-authorization';
2
- import { createMockLogger } from '@test/helpers/mock-logger';
3
- import { getTestPrisma, resetTestDatabase } from '@test/helpers/test-db';
4
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
-
6
- import type { LoggerService } from '@/common/logger.service';
7
- import type { AuthorizationService } from '@/services/authorization.service';
8
- import { UsersService } from '@/services/users.service';
9
-
10
- /**
11
- * Integration tests for UsersService
12
- * These tests use a real PostgreSQL database (app_dev_test)
13
- * The database is automatically created and migrated in test/setup.ts
14
- */
15
- describe('UsersService Integration Tests', () => {
16
- let service: UsersService;
17
- let logger: LoggerService;
18
- let authorizationService: AuthorizationService;
19
-
20
- beforeEach(async () => {
21
- // Reset database between tests
22
- await resetTestDatabase();
23
-
24
- // Create service with real Prisma client
25
- logger = createMockLogger();
26
- authorizationService = createMockAuthorizationService();
27
- const prisma = getTestPrisma();
28
- service = new UsersService(prisma, logger, authorizationService);
29
- });
30
-
31
- afterEach(async () => {
32
- // Additional cleanup if needed
33
- });
34
-
35
- describe('User CRUD Operations', () => {
36
- it('should create and retrieve a user', async () => {
37
- // Create user
38
- const createdUser = await service.createUser({
39
- email: 'integration@test.com',
40
- name: 'Integration Test User',
41
- role: 'user',
42
- });
43
-
44
- expect(createdUser.id).toBeDefined();
45
- expect(createdUser.email).toBe('integration@test.com');
46
- expect(createdUser.name).toBe('Integration Test User');
47
- expect(createdUser.role).toBe('user');
48
-
49
- // Retrieve user by ID
50
- const retrievedUser = await service.getUserById(createdUser.id);
51
-
52
- expect(retrievedUser).toBeDefined();
53
- expect(retrievedUser?.id).toBe(createdUser.id);
54
- expect(retrievedUser?.email).toBe('integration@test.com');
55
- });
56
-
57
- it('should update an existing user', async () => {
58
- // Create user
59
- const user = await service.createUser({
60
- email: 'update@test.com',
61
- name: 'Original Name',
62
- role: 'user',
63
- });
64
-
65
- // Update user
66
- const updatedUser = await service.updateUser(
67
- 'actor-id',
68
- 'super_admin',
69
- user.id,
70
- {
71
- name: 'Updated Name',
72
- }
73
- );
74
-
75
- expect(updatedUser).toBeDefined();
76
- expect(updatedUser?.name).toBe('Updated Name');
77
- expect(updatedUser?.email).toBe('update@test.com'); // Email unchanged
78
-
79
- // Verify update persisted
80
- const retrievedUser = await service.getUserById(user.id);
81
- expect(retrievedUser?.name).toBe('Updated Name');
82
- });
83
-
84
- it('should delete a user', async () => {
85
- // Create user
86
- const user = await service.createUser({
87
- email: 'delete@test.com',
88
- name: 'To Be Deleted',
89
- role: 'user',
90
- });
91
-
92
- // Delete user
93
- await service.deleteUser('actor-id', 'super_admin', user.id);
94
-
95
- // Verify user is deleted (should throw NotFoundError)
96
- await expect(service.getUserById(user.id)).rejects.toThrow(
97
- 'User not found'
98
- );
99
- });
100
-
101
- it('should throw NotFoundError when updating non-existent user', async () => {
102
- await expect(
103
- service.updateUser('actor-id', 'super_admin', 'non-existent-id', {
104
- name: 'New Name',
105
- })
106
- ).rejects.toThrow('User not found');
107
- });
108
-
109
- it('should throw NotFoundError when deleting non-existent user', async () => {
110
- await expect(
111
- service.deleteUser('actor-id', 'super_admin', 'non-existent-id')
112
- ).rejects.toThrow('User not found');
113
- });
114
- });
115
-
116
- describe('User Pagination and Filtering', () => {
117
- beforeEach(async () => {
118
- // Create test users
119
- await service.createUser({
120
- email: 'alice@test.com',
121
- name: 'Alice Smith',
122
- role: 'user',
123
- });
124
- await service.createUser({
125
- email: 'bob@test.com',
126
- name: 'Bob Johnson',
127
- role: 'admin',
128
- });
129
- await service.createUser({
130
- email: 'charlie@test.com',
131
- name: 'Charlie Brown',
132
- role: 'user',
133
- });
134
- });
135
-
136
- it('should return paginated users', async () => {
137
- const result = await service.getUsers({
138
- page: 1,
139
- limit: 10,
140
- sortBy: 'createdAt',
141
- sortOrder: 'desc',
142
- });
143
-
144
- expect(result.data).toHaveLength(3);
145
- expect(result.pagination.total).toBe(3);
146
- expect(result.pagination.totalPages).toBe(1);
147
- expect(result.pagination.page).toBe(1);
148
- expect(result.pagination.limit).toBe(10);
149
- });
150
-
151
- it('should handle pagination correctly', async () => {
152
- const result = await service.getUsers({
153
- page: 1,
154
- limit: 2,
155
- sortBy: 'createdAt',
156
- sortOrder: 'desc',
157
- });
158
-
159
- expect(result.data).toHaveLength(2);
160
- expect(result.pagination.total).toBe(3);
161
- expect(result.pagination.totalPages).toBe(2);
162
- expect(result.pagination.page).toBe(1);
163
- expect(result.pagination.limit).toBe(2);
164
- });
165
-
166
- it('should filter users by search query', async () => {
167
- const result = await service.getUsers({
168
- page: 1,
169
- limit: 10,
170
- sortBy: 'createdAt',
171
- sortOrder: 'desc',
172
- search: 'alice',
173
- });
174
-
175
- expect(result.data).toHaveLength(1);
176
- expect(result.data[0].email).toBe('alice@test.com');
177
- });
178
-
179
- it('should sort users by email', async () => {
180
- const result = await service.getUsers({
181
- page: 1,
182
- limit: 10,
183
- sortBy: 'email',
184
- sortOrder: 'asc',
185
- });
186
-
187
- expect(result.data[0].email).toBe('alice@test.com');
188
- expect(result.data[1].email).toBe('bob@test.com');
189
- expect(result.data[2].email).toBe('charlie@test.com');
190
- });
191
- });
192
-
193
- describe('Database Constraints', () => {
194
- it('should enforce unique email constraint', async () => {
195
- await service.createUser({
196
- email: 'unique@test.com',
197
- name: 'First User',
198
- role: 'user',
199
- });
200
-
201
- // Attempting to create another user with same email should fail
202
- await expect(
203
- service.createUser({
204
- email: 'unique@test.com',
205
- name: 'Second User',
206
- role: 'user',
207
- })
208
- ).rejects.toThrow();
209
- });
210
- });
211
- });
@@ -1,31 +0,0 @@
1
- import { afterAll, beforeAll } from 'vitest';
2
-
3
- import { cleanupTestDatabase, setupTestDatabase } from './helpers/test-db';
4
-
5
- beforeAll(async () => {
6
- // Set test environment variables before database setup
7
- process.env.NODE_ENV = 'test';
8
-
9
- // Set minimal required env vars for tests
10
- // These are only needed for test infrastructure, not for actual test assertions
11
- process.env.BETTER_AUTH_SECRET =
12
- process.env.BETTER_AUTH_SECRET || 'test-secret-minimum-32-characters-long';
13
- process.env.BETTER_AUTH_URL =
14
- process.env.BETTER_AUTH_URL || 'http://localhost:8080';
15
- process.env.API_URL = process.env.API_URL || 'http://localhost:8080';
16
- process.env.FRONTEND_URL =
17
- process.env.FRONTEND_URL || 'http://localhost:3000';
18
- process.env.COOKIE_SECRET = process.env.COOKIE_SECRET || 'test-cookie-secret';
19
- process.env.PORT = process.env.PORT || '8080';
20
- process.env.LOG_LEVEL = process.env.LOG_LEVEL || 'minimal';
21
-
22
- // DATABASE_URL is set in test-db.ts based on your .env.local
23
-
24
- // Setup test database (runs migrations)
25
- await setupTestDatabase();
26
- });
27
-
28
- afterAll(async () => {
29
- // Cleanup test database
30
- await cleanupTestDatabase();
31
- });
@@ -1,26 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "declaration": true,
7
- "removeComments": true,
8
- "allowSyntheticDefaultImports": true,
9
- "esModuleInterop": true,
10
- "target": "ES2022",
11
- "lib": ["ES2022", "DOM"],
12
- "sourceMap": true,
13
- "outDir": "./dist",
14
- "baseUrl": "./",
15
- "incremental": true,
16
- "skipLibCheck": true,
17
- "resolveJsonModule": true,
18
- "jsx": "react",
19
- "paths": {
20
- "@/*": ["src/*"],
21
- "@test/*": ["test/*"]
22
- }
23
- },
24
- "include": ["src/**/*", "test/**/*", "emails/**/*"],
25
- "exclude": ["node_modules", "dist", "src/generated"]
26
- }