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,227 +0,0 @@
1
- import type { FastifyBaseLogger } from 'fastify';
2
- import pino, { type Logger } from 'pino';
3
-
4
- import { loadEnv } from '@/config/env';
5
-
6
- type VerbosityLevel = 'minimal' | 'normal' | 'detailed' | 'verbose';
7
- type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
8
-
9
- interface LogContext {
10
- [key: string]: unknown;
11
- }
12
-
13
- interface PerformanceMetrics {
14
- operation: string;
15
- duration: number;
16
- [key: string]: unknown;
17
- }
18
-
19
- export class LoggerService {
20
- private readonly logger: Logger | FastifyBaseLogger;
21
- private readonly globalVerbosity: VerbosityLevel;
22
- private readonly isDevelopment: boolean;
23
- private verbosityOverride?: VerbosityLevel;
24
- private context?: string;
25
-
26
- constructor(existingLogger?: Logger | FastifyBaseLogger) {
27
- const env = loadEnv();
28
- this.isDevelopment = env.NODE_ENV === 'development';
29
- this.globalVerbosity = env.LOG_LEVEL;
30
-
31
- if (existingLogger) {
32
- this.logger = existingLogger;
33
- } else {
34
- this.logger = pino({
35
- level: 'trace',
36
- formatters: {
37
- level: (label) => ({ level: label }),
38
- },
39
- serializers: {
40
- err: pino.stdSerializers.err,
41
- error: pino.stdSerializers.err,
42
- req: pino.stdSerializers.req,
43
- res: pino.stdSerializers.res,
44
- },
45
- transport: this.isDevelopment
46
- ? {
47
- target: 'pino-pretty',
48
- options: {
49
- colorize: true,
50
- ignore: 'pid,hostname',
51
- singleLine: false,
52
- messageFormat: '{if context}[{context}] {end}{msg}',
53
- translateTime: 'HH:MM:ss',
54
- },
55
- }
56
- : undefined,
57
- });
58
- }
59
- }
60
-
61
- minimal(): this {
62
- const instance = this.clone();
63
- instance.verbosityOverride = 'minimal';
64
- return instance;
65
- }
66
-
67
- normal(): this {
68
- const instance = this.clone();
69
- instance.verbosityOverride = 'normal';
70
- return instance;
71
- }
72
-
73
- detailed(): this {
74
- const instance = this.clone();
75
- instance.verbosityOverride = 'detailed';
76
- return instance;
77
- }
78
-
79
- verbose(): this {
80
- const instance = this.clone();
81
- instance.verbosityOverride = 'verbose';
82
- return instance;
83
- }
84
-
85
- setContext(context: string): void {
86
- this.context = context;
87
- }
88
-
89
- child(context: string): LoggerService {
90
- const instance = this.clone();
91
- instance.context = context;
92
- return instance;
93
- }
94
-
95
- log(message: string, context?: LogContext): void {
96
- this.info(message, context);
97
- }
98
-
99
- info(message: string, context?: LogContext): void {
100
- if (this.shouldLog('info')) {
101
- this.writeLog('info', message, context);
102
- }
103
- }
104
-
105
- error(message: string, error?: Error | string, context?: LogContext): void {
106
- if (this.shouldLog('error')) {
107
- const errorContext =
108
- error instanceof Error
109
- ? { err: error, ...context }
110
- : error
111
- ? { trace: error, ...context }
112
- : context;
113
-
114
- this.writeLog('error', message, errorContext);
115
- }
116
- }
117
-
118
- warn(message: string, context?: LogContext): void {
119
- if (this.shouldLog('warn')) {
120
- this.writeLog('warn', message, context);
121
- }
122
- }
123
-
124
- debug(message: string, context?: LogContext): void {
125
- if (this.shouldLog('debug')) {
126
- this.writeLog('debug', message, context);
127
- }
128
- }
129
-
130
- trace(message: string, context?: LogContext): void {
131
- if (this.shouldLog('trace')) {
132
- this.writeLog('trace', message, context);
133
- }
134
- }
135
-
136
- perf(message: string, metrics: PerformanceMetrics): void {
137
- if (this.shouldLog('debug')) {
138
- this.writeLog('debug', message, {
139
- performance: true,
140
- ...metrics,
141
- });
142
- }
143
- }
144
-
145
- getVerbosity(): VerbosityLevel {
146
- return this.verbosityOverride ?? this.globalVerbosity;
147
- }
148
-
149
- getRawLogger(): Logger | FastifyBaseLogger {
150
- return this.logger;
151
- }
152
-
153
- private shouldLog(level: LogLevel): boolean {
154
- const effectiveVerbosity = this.verbosityOverride ?? this.globalVerbosity;
155
-
156
- const verbosityOrder: VerbosityLevel[] = [
157
- 'minimal',
158
- 'normal',
159
- 'detailed',
160
- 'verbose',
161
- ];
162
- const currentLevel =
163
- verbosityOrder.indexOf(effectiveVerbosity) >= 0
164
- ? verbosityOrder.indexOf(effectiveVerbosity)
165
- : 1;
166
-
167
- switch (level) {
168
- case 'error':
169
- case 'warn':
170
- return currentLevel >= 0;
171
- case 'info':
172
- return currentLevel >= 1;
173
- case 'debug':
174
- return currentLevel >= 2;
175
- case 'trace':
176
- return currentLevel >= 3;
177
- default:
178
- return false;
179
- }
180
- }
181
-
182
- private writeLog(
183
- level: 'info' | 'error' | 'warn' | 'debug' | 'trace',
184
- message: string,
185
- context?: LogContext
186
- ): void {
187
- const effectiveVerbosity = this.verbosityOverride ?? this.globalVerbosity;
188
- const enrichedContext: LogContext = {};
189
-
190
- if (effectiveVerbosity === 'minimal') {
191
- // Minimal: message only + critical error fields
192
- if (context?.err) enrichedContext.err = context.err;
193
- if (context?.error) enrichedContext.error = context.error;
194
- } else if (effectiveVerbosity === 'normal') {
195
- // Normal: [context] + message + error fields (no additional fields)
196
- if (this.context) {
197
- enrichedContext.context = this.context;
198
- }
199
- if (context?.err) enrichedContext.err = context.err;
200
- if (context?.error) enrichedContext.error = context.error;
201
- if (context?.trace) enrichedContext.trace = context.trace;
202
- } else {
203
- // Detailed & Verbose: [context] + message + all fields
204
- if (this.context) {
205
- enrichedContext.context = this.context;
206
- }
207
- if (context) {
208
- Object.assign(enrichedContext, context);
209
- }
210
- }
211
-
212
- (this.logger[level] as (obj: object, msg?: string) => void)(
213
- enrichedContext,
214
- message
215
- );
216
- }
217
-
218
- private clone(): this {
219
- const instance = Object.create(Object.getPrototypeOf(this));
220
- instance.logger = this.logger;
221
- instance.globalVerbosity = this.globalVerbosity;
222
- instance.isDevelopment = this.isDevelopment;
223
- instance.context = this.context;
224
- instance.verbosityOverride = this.verbosityOverride;
225
- return instance;
226
- }
227
- }
@@ -1,60 +0,0 @@
1
- import dotenvFlow from 'dotenv-flow';
2
- import { z } from 'zod';
3
-
4
- const EnvSchema = z.object({
5
- NODE_ENV: z
6
- .enum(['development', 'production', 'test'])
7
- .default('development'),
8
- API_URL: z.string().url(),
9
- FRONTEND_URL: z.string().url(),
10
- DATABASE_URL: z.string().min(1),
11
- PORT: z.string().transform(Number).pipe(z.number().int().positive()),
12
- COOKIE_SECRET: z
13
- .string()
14
- .min(16, 'COOKIE_SECRET must be at least 16 characters'),
15
- LOG_LEVEL: z
16
- .enum(['minimal', 'normal', 'detailed', 'verbose'])
17
- .default('normal'),
18
- BETTER_AUTH_SECRET: z
19
- .string()
20
- .min(32, 'BETTER_AUTH_SECRET must be at least 32 characters'),
21
- BETTER_AUTH_URL: z.string().url(),
22
-
23
- // OAuth Provider Credentials (Optional)
24
- // Only required if you enable social login providers in Better Auth configuration
25
- // Leave empty to use email/password authentication only
26
- GITHUB_CLIENT_ID: z.string().optional(),
27
- GITHUB_CLIENT_SECRET: z.string().optional(),
28
- GOOGLE_CLIENT_ID: z.string().optional(),
29
- GOOGLE_CLIENT_SECRET: z.string().optional(),
30
-
31
- // Email Service Configuration (Optional)
32
- // If RESEND_API_KEY is not provided, emails will be logged to console (dev mode)
33
- // EMAIL_FROM: The sender email address (e.g., 'noreply@yourdomain.com')
34
- // Note: You MUST verify your domain in Resend dashboard before sending emails
35
- RESEND_API_KEY: z.string().optional(),
36
- EMAIL_FROM: z.string().email().optional(),
37
-
38
- // File Storage Configuration (Optional)
39
- STORAGE_TYPE: z.enum(['local', 's3', 'r2']).optional().default('local'),
40
- S3_BUCKET: z.string().optional(),
41
- S3_REGION: z.string().optional(),
42
- S3_ACCESS_KEY_ID: z.string().optional(),
43
- S3_SECRET_ACCESS_KEY: z.string().optional(),
44
- S3_ENDPOINT: z.string().url().optional(), // For R2/MinIO compatibility
45
- });
46
-
47
- export type Env = z.infer<typeof EnvSchema>;
48
-
49
- export function loadEnv(path?: string): Env {
50
- dotenvFlow.config({ path: path || process.cwd() });
51
-
52
- const result = EnvSchema.safeParse(process.env);
53
-
54
- if (!result.success) {
55
- console.error('❌ Invalid environment variables:', result.error.format());
56
- throw new Error('Invalid environment variables');
57
- }
58
-
59
- return result.data;
60
- }
@@ -1,29 +0,0 @@
1
- export const RATE_LIMIT_CONFIG = {
2
- // Role-based rate limits (requests per minute)
3
- admin: {
4
- max: 200,
5
- timeWindow: 60 * 1000,
6
- },
7
- user: {
8
- max: 60,
9
- timeWindow: 60 * 1000,
10
- },
11
- anonymous: {
12
- max: 30,
13
- timeWindow: 60 * 1000,
14
- },
15
-
16
- // Route-specific overrides
17
- routes: {
18
- auth: {
19
- max: 10,
20
- timeWindow: 60 * 1000,
21
- },
22
- uploads: {
23
- max: 20,
24
- timeWindow: 60 * 1000,
25
- },
26
- },
27
- } as const;
28
-
29
- export type RateLimitRole = 'admin' | 'user' | 'anonymous';
@@ -1,122 +0,0 @@
1
- import { ForbiddenError, UnauthorizedError } from '@repo/packages-utils/errors';
2
- import type { FastifyReply, FastifyRequest } from 'fastify';
3
-
4
- export interface AuthUser {
5
- id: string;
6
- email: string;
7
- name: string;
8
- role: string;
9
- session?: {
10
- id: string;
11
- };
12
- }
13
-
14
- declare module 'fastify' {
15
- interface FastifyRequest {
16
- user?: AuthUser;
17
- }
18
- }
19
-
20
- export async function requireAuth(
21
- request: FastifyRequest,
22
- reply: FastifyReply
23
- ): Promise<void> {
24
- try {
25
- const session = await request.server.auth.api.getSession({
26
- headers: request.headers as unknown as Headers,
27
- });
28
-
29
- if (!session?.user) {
30
- throw new UnauthorizedError('Authentication required');
31
- }
32
-
33
- // Fetch user from database to check ban status and get latest role
34
- const user = await request.server.prisma.user.findUnique({
35
- where: { id: session.user.id },
36
- select: {
37
- id: true,
38
- email: true,
39
- name: true,
40
- role: true,
41
- banned: true,
42
- banReason: true,
43
- banExpires: true,
44
- },
45
- });
46
-
47
- if (!user) {
48
- request.log.warn(
49
- {
50
- userId: session.user.id,
51
- },
52
- 'User not found in database'
53
- );
54
- throw new UnauthorizedError('User not found');
55
- }
56
-
57
- // Check if user is banned
58
- if (user.banned) {
59
- // Check if ban has expired
60
- if (user.banExpires && new Date() > user.banExpires) {
61
- // Automatically unban user
62
- await request.server.prisma.user.update({
63
- where: { id: user.id },
64
- data: {
65
- banned: false,
66
- banReason: null,
67
- banExpires: null,
68
- },
69
- });
70
- request.log.info({ userId: user.id }, 'User ban expired and removed');
71
- } else {
72
- request.log.warn(
73
- {
74
- userId: user.id,
75
- banReason: user.banReason,
76
- },
77
- 'Banned user attempted to access system'
78
- );
79
- throw new ForbiddenError('Your account has been banned', {
80
- reason: user.banReason,
81
- expiresAt: user.banExpires?.toISOString(),
82
- });
83
- }
84
- }
85
-
86
- request.user = {
87
- id: user.id,
88
- email: user.email,
89
- name: user.name ?? '',
90
- role: user.role,
91
- session: session.session
92
- ? {
93
- id: session.session.id,
94
- }
95
- : undefined,
96
- };
97
- } catch (error) {
98
- if (error instanceof UnauthorizedError || error instanceof ForbiddenError) {
99
- throw error;
100
- }
101
- request.log.error(error, 'Auth hook error');
102
- throw new UnauthorizedError('Invalid or expired session');
103
- }
104
- }
105
-
106
- export function requireRole(roles: string[]) {
107
- return async (
108
- request: FastifyRequest,
109
- reply: FastifyReply
110
- ): Promise<void> => {
111
- if (!request.user) {
112
- throw new UnauthorizedError('Authentication required');
113
- }
114
-
115
- if (!roles.includes(request.user.role)) {
116
- throw new ForbiddenError('Insufficient permissions', {
117
- required: roles,
118
- current: request.user.role,
119
- });
120
- }
121
- };
122
- }
@@ -1,198 +0,0 @@
1
- import { betterAuth } from 'better-auth';
2
- import { prismaAdapter } from 'better-auth/adapters/prisma';
3
- import { admin } from 'better-auth/plugins';
4
- import type { FastifyPluginAsync, FastifyRequest } from 'fastify';
5
- import fp from 'fastify-plugin';
6
-
7
- import { loadEnv } from '@/config/env.js';
8
- import { RATE_LIMIT_CONFIG } from '@/config/rate-limit.js';
9
- import { type PrismaClient } from '@/generated/client/client.js';
10
-
11
- declare module 'fastify' {
12
- interface FastifyInstance {
13
- auth: ReturnType<typeof betterAuth>;
14
- }
15
- }
16
-
17
- const authPlugin: FastifyPluginAsync = async (app) => {
18
- const env = loadEnv();
19
-
20
- const socialProviders: Record<string, unknown> = {};
21
-
22
- if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
23
- socialProviders.google = {
24
- clientId: env.GOOGLE_CLIENT_ID,
25
- clientSecret: env.GOOGLE_CLIENT_SECRET,
26
- };
27
- }
28
-
29
- if (env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET) {
30
- socialProviders.github = {
31
- clientId: env.GITHUB_CLIENT_ID,
32
- clientSecret: env.GITHUB_CLIENT_SECRET,
33
- };
34
- }
35
-
36
- app.log.info(
37
- `[+] OAuth configured with providers: ${Object.keys(socialProviders).join(', ')}`
38
- );
39
-
40
- const auth = betterAuth({
41
- database: prismaAdapter(app.prisma as unknown as PrismaClient, {
42
- provider: 'postgresql',
43
- }),
44
- baseURL: env.BETTER_AUTH_URL,
45
- secret: env.BETTER_AUTH_SECRET,
46
- emailAndPassword: {
47
- enabled: true,
48
- requireEmailVerification: false,
49
- sendEmailVerificationOnSignUp: true,
50
- autoSignInAfterVerification: true,
51
- resetPasswordTokenExpiresIn: 3600,
52
- sendResetPassword: async ({ user, token }) => {
53
- const resetUrl = `${env.FRONTEND_URL}/reset-password?token=${token}`;
54
- await app.emailService.sendPasswordResetEmail(user.email, resetUrl);
55
- },
56
- },
57
- emailVerification: {
58
- sendOnSignUp: true,
59
- autoSignInAfterVerification: true,
60
- sendVerificationEmail: async ({ user, url }) => {
61
- const urlObj = new URL(url);
62
- urlObj.searchParams.set('callbackURL', env.FRONTEND_URL);
63
- await app.emailService.sendVerificationEmail(
64
- user.email,
65
- urlObj.toString()
66
- );
67
- },
68
- },
69
- session: {
70
- expiresIn: 60 * 60 * 24 * 7, // 7 days
71
- updateAge: 60 * 60 * 24, // Update every 24 hours
72
- cookieCache: {
73
- enabled: true,
74
- maxAge: 60 * 60 * 24 * 30, // 30 days
75
- },
76
- },
77
- advanced: {
78
- // IMPORTANT: For cross-domain deployments
79
- // useSecureCookies forces secure cookies even in development
80
- useSecureCookies: env.NODE_ENV === 'production',
81
- database: {
82
- generateId: () =>
83
- `auth-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
84
- },
85
- redirectURLs: {
86
- onError: env.FRONTEND_URL,
87
- afterSignIn: env.FRONTEND_URL,
88
- },
89
- },
90
- trustedOrigins: [env.FRONTEND_URL],
91
- socialProviders,
92
- plugins: [
93
- admin({
94
- defaultRole: 'user',
95
- adminRoles: ['admin', 'super_admin'],
96
- }),
97
- ],
98
- });
99
-
100
- app.decorate('auth', auth);
101
-
102
- app.all(
103
- '/api/auth/*',
104
- {
105
- config: {
106
- rateLimit: {
107
- max: RATE_LIMIT_CONFIG.routes.auth.max,
108
- timeWindow: RATE_LIMIT_CONFIG.routes.auth.timeWindow,
109
- },
110
- },
111
- },
112
- async (request, reply) => {
113
- try {
114
- // Convert Fastify request to Web Request for Better Auth
115
- const webRequest = await toWebRequest(request);
116
-
117
- // Handle the request with Better Auth
118
- const response = await auth.handler(webRequest);
119
-
120
- // Set status
121
- reply.status(response.status);
122
-
123
- // Process headers and modify Set-Cookie for cross-domain
124
- response.headers.forEach((value, key) => {
125
- if (key.toLowerCase() === 'set-cookie') {
126
- // For cross-domain cookie support, we need to modify cookie attributes
127
- // Better Auth sets cookies, but we need to ensure SameSite=None for cross-domain
128
- const cookieValue = value;
129
-
130
- if (env.NODE_ENV === 'production') {
131
- let modifiedCookie = cookieValue;
132
-
133
- if (!modifiedCookie.includes('Secure')) {
134
- modifiedCookie += '; Secure';
135
- }
136
-
137
- if (modifiedCookie.includes('SameSite=Lax')) {
138
- modifiedCookie = modifiedCookie.replace(
139
- 'SameSite=Lax',
140
- 'SameSite=None'
141
- );
142
- } else if (!modifiedCookie.includes('SameSite=')) {
143
- modifiedCookie += '; SameSite=None';
144
- }
145
-
146
- reply.header(key, modifiedCookie);
147
- } else {
148
- reply.header(key, cookieValue);
149
- }
150
- } else {
151
- reply.header(key, value);
152
- }
153
- });
154
-
155
- const body = await response.text();
156
- return reply.send(body);
157
- } catch (error) {
158
- app.log.error(error, 'Better Auth handler error');
159
- return reply.status(500).send({
160
- statusCode: 500,
161
- error: 'Internal Server Error',
162
- message: 'Authentication error occurred',
163
- });
164
- }
165
- }
166
- );
167
-
168
- app.log.info('[+] Better Auth configured');
169
- };
170
-
171
- async function toWebRequest(request: FastifyRequest): Promise<Request> {
172
- const url = new URL(request.url, `${request.protocol}://${request.hostname}`);
173
-
174
- const headers = new Headers();
175
- Object.entries(request.headers).forEach(([key, value]) => {
176
- if (value) {
177
- const headerValue = Array.isArray(value)
178
- ? value.join(', ')
179
- : String(value);
180
- headers.set(key, headerValue);
181
- }
182
- });
183
-
184
- let body: string | null = null;
185
- if (request.method !== 'GET' && request.method !== 'HEAD') {
186
- if (request.body) {
187
- body = JSON.stringify(request.body);
188
- }
189
- }
190
-
191
- return new Request(url.toString(), {
192
- method: request.method,
193
- headers,
194
- body,
195
- });
196
- }
197
-
198
- export default fp(authPlugin);
@@ -1,45 +0,0 @@
1
- import 'dotenv-flow/config';
2
-
3
- import { PrismaPg } from '@prisma/adapter-pg';
4
- import { type FastifyPluginAsync } from 'fastify';
5
- import fp from 'fastify-plugin';
6
- import { Pool } from 'pg';
7
-
8
- import { loadEnv } from '@/config/env';
9
- import { PrismaClient } from '@/generated/client/client.js';
10
-
11
- declare module 'fastify' {
12
- interface FastifyInstance {
13
- prisma: PrismaClient;
14
- pgPool: Pool;
15
- }
16
- }
17
-
18
- const databasePlugin: FastifyPluginAsync = async (app) => {
19
- const env = loadEnv();
20
-
21
- const pool = new Pool({ connectionString: env.DATABASE_URL });
22
- const adapter = new PrismaPg(pool);
23
-
24
- const prisma = new PrismaClient({
25
- adapter,
26
- log:
27
- env.LOG_LEVEL === 'verbose'
28
- ? ['query', 'info', 'warn', 'error']
29
- : ['warn', 'error'],
30
- });
31
-
32
- await prisma.$connect();
33
- app.log.info('[+] Database connected successfully');
34
-
35
- app.decorate('prisma', prisma);
36
- app.decorate('pgPool', pool);
37
-
38
- app.addHook('onClose', async (instance) => {
39
- instance.log.info('[-] Disconnecting from database...');
40
- await instance.prisma.$disconnect();
41
- await instance.pgPool.end();
42
- });
43
- };
44
-
45
- export default fp(databasePlugin);