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.
- package/dist/index.js +35 -77
- package/package.json +5 -6
- package/template/.dockerignore +0 -59
- package/template/.github/workflows/ci.yml +0 -157
- package/template/.husky/pre-commit +0 -1
- package/template/.husky/pre-push +0 -1
- package/template/.lintstagedrc.cjs +0 -4
- package/template/.nvmrc +0 -1
- package/template/.prettierrc +0 -9
- package/template/.vscode/settings.json +0 -13
- package/template/CLAUDE.md +0 -175
- package/template/CONTRIBUTING.md +0 -32
- package/template/Dockerfile +0 -90
- package/template/GETTING_STARTED.md +0 -35
- package/template/LICENSE +0 -21
- package/template/README.md +0 -116
- package/template/apps/api/.dockerignore +0 -51
- package/template/apps/api/.env.local.example +0 -62
- package/template/apps/api/emails/account-deleted-email.tsx +0 -69
- package/template/apps/api/emails/components/email-layout.tsx +0 -154
- package/template/apps/api/emails/config.ts +0 -22
- package/template/apps/api/emails/password-changed-email.tsx +0 -88
- package/template/apps/api/emails/password-reset-email.tsx +0 -86
- package/template/apps/api/emails/verification-email.tsx +0 -85
- package/template/apps/api/emails/welcome-email.tsx +0 -70
- package/template/apps/api/package.json +0 -84
- package/template/apps/api/prisma/migrations/20251012111439_init/migration.sql +0 -13
- package/template/apps/api/prisma/migrations/20251018162629_add_better_auth_fields/migration.sql +0 -67
- package/template/apps/api/prisma/migrations/20251019142208_add_user_role_enum/migration.sql +0 -5
- package/template/apps/api/prisma/migrations/20251019182151_user_auth/migration.sql +0 -7
- package/template/apps/api/prisma/migrations/20251019211416_faster_session_lookup/migration.sql +0 -2
- package/template/apps/api/prisma/migrations/20251119124337_add_upload_model/migration.sql +0 -26
- package/template/apps/api/prisma/migrations/20251120071241_add_scope_to_account/migration.sql +0 -2
- package/template/apps/api/prisma/migrations/20251120072608_add_oauth_token_expiration_fields/migration.sql +0 -10
- package/template/apps/api/prisma/migrations/20251120144705_add_audit_logs/migration.sql +0 -29
- package/template/apps/api/prisma/migrations/20251127123614_remove_impersonated_by/migration.sql +0 -8
- package/template/apps/api/prisma/migrations/20251127125630_remove_audit_logs/migration.sql +0 -11
- package/template/apps/api/prisma/migrations/migration_lock.toml +0 -3
- package/template/apps/api/prisma/schema.prisma +0 -116
- package/template/apps/api/prisma/seed.ts +0 -159
- package/template/apps/api/prisma.config.ts +0 -14
- package/template/apps/api/src/app.ts +0 -377
- package/template/apps/api/src/common/logger.service.ts +0 -227
- package/template/apps/api/src/config/env.ts +0 -60
- package/template/apps/api/src/config/rate-limit.ts +0 -29
- package/template/apps/api/src/hooks/auth.ts +0 -122
- package/template/apps/api/src/plugins/auth.ts +0 -198
- package/template/apps/api/src/plugins/database.ts +0 -45
- package/template/apps/api/src/plugins/logger.ts +0 -33
- package/template/apps/api/src/plugins/multipart.ts +0 -16
- package/template/apps/api/src/plugins/scalar.ts +0 -20
- package/template/apps/api/src/plugins/schedule.ts +0 -52
- package/template/apps/api/src/plugins/services.ts +0 -66
- package/template/apps/api/src/plugins/swagger.ts +0 -56
- package/template/apps/api/src/routes/accounts.ts +0 -91
- package/template/apps/api/src/routes/admin-sessions.ts +0 -92
- package/template/apps/api/src/routes/metrics.ts +0 -71
- package/template/apps/api/src/routes/password.ts +0 -46
- package/template/apps/api/src/routes/sessions.ts +0 -53
- package/template/apps/api/src/routes/stats.ts +0 -38
- package/template/apps/api/src/routes/uploads-serve.ts +0 -27
- package/template/apps/api/src/routes/uploads.ts +0 -154
- package/template/apps/api/src/routes/users.ts +0 -114
- package/template/apps/api/src/routes/verification.ts +0 -90
- package/template/apps/api/src/server.ts +0 -34
- package/template/apps/api/src/services/accounts.service.ts +0 -125
- package/template/apps/api/src/services/authorization.service.ts +0 -162
- package/template/apps/api/src/services/email.service.ts +0 -170
- package/template/apps/api/src/services/file-storage.service.ts +0 -267
- package/template/apps/api/src/services/metrics.service.ts +0 -175
- package/template/apps/api/src/services/password.service.ts +0 -56
- package/template/apps/api/src/services/sessions.service.spec.ts +0 -134
- package/template/apps/api/src/services/sessions.service.ts +0 -276
- package/template/apps/api/src/services/stats.service.ts +0 -273
- package/template/apps/api/src/services/uploads.service.ts +0 -163
- package/template/apps/api/src/services/users.service.spec.ts +0 -249
- package/template/apps/api/src/services/users.service.ts +0 -198
- package/template/apps/api/src/utils/file-validation.ts +0 -108
- package/template/apps/api/start.sh +0 -33
- package/template/apps/api/test/helpers/fastify-app.ts +0 -24
- package/template/apps/api/test/helpers/mock-authorization.ts +0 -16
- package/template/apps/api/test/helpers/mock-logger.ts +0 -28
- package/template/apps/api/test/helpers/mock-prisma.ts +0 -30
- package/template/apps/api/test/helpers/test-db.ts +0 -125
- package/template/apps/api/test/integration/auth-flow.integration.spec.ts +0 -449
- package/template/apps/api/test/integration/password.integration.spec.ts +0 -427
- package/template/apps/api/test/integration/rate-limit.integration.spec.ts +0 -51
- package/template/apps/api/test/integration/sessions.integration.spec.ts +0 -445
- package/template/apps/api/test/integration/users.integration.spec.ts +0 -211
- package/template/apps/api/test/setup.ts +0 -31
- package/template/apps/api/tsconfig.json +0 -26
- package/template/apps/api/vitest.config.ts +0 -35
- package/template/apps/web/.env.local.example +0 -11
- package/template/apps/web/components.json +0 -24
- package/template/apps/web/next.config.ts +0 -22
- package/template/apps/web/package.json +0 -56
- package/template/apps/web/postcss.config.js +0 -5
- package/template/apps/web/public/apple-icon.png +0 -0
- package/template/apps/web/public/icon.png +0 -0
- package/template/apps/web/public/robots.txt +0 -3
- package/template/apps/web/src/app/(admin)/admin/layout.tsx +0 -222
- package/template/apps/web/src/app/(admin)/admin/page.tsx +0 -157
- package/template/apps/web/src/app/(admin)/admin/sessions/page.tsx +0 -18
- package/template/apps/web/src/app/(admin)/admin/users/page.tsx +0 -20
- package/template/apps/web/src/app/(auth)/forgot-password/page.tsx +0 -177
- package/template/apps/web/src/app/(auth)/login/page.tsx +0 -159
- package/template/apps/web/src/app/(auth)/reset-password/page.tsx +0 -245
- package/template/apps/web/src/app/(auth)/signup/page.tsx +0 -153
- package/template/apps/web/src/app/dashboard/change-password/page.tsx +0 -255
- package/template/apps/web/src/app/dashboard/page.tsx +0 -296
- package/template/apps/web/src/app/error.tsx +0 -32
- package/template/apps/web/src/app/examples/file-upload/page.tsx +0 -200
- package/template/apps/web/src/app/favicon.ico +0 -0
- package/template/apps/web/src/app/global-error.tsx +0 -96
- package/template/apps/web/src/app/globals.css +0 -22
- package/template/apps/web/src/app/icon.png +0 -0
- package/template/apps/web/src/app/layout.tsx +0 -34
- package/template/apps/web/src/app/not-found.tsx +0 -28
- package/template/apps/web/src/app/page.tsx +0 -192
- package/template/apps/web/src/components/admin/activity-feed.tsx +0 -101
- package/template/apps/web/src/components/admin/charts/auth-breakdown-chart.tsx +0 -114
- package/template/apps/web/src/components/admin/charts/chart-tooltip.tsx +0 -124
- package/template/apps/web/src/components/admin/charts/realtime-metrics-chart.tsx +0 -511
- package/template/apps/web/src/components/admin/charts/role-distribution-chart.tsx +0 -102
- package/template/apps/web/src/components/admin/charts/session-activity-chart.tsx +0 -90
- package/template/apps/web/src/components/admin/charts/user-growth-chart.tsx +0 -108
- package/template/apps/web/src/components/admin/health-indicator.tsx +0 -175
- package/template/apps/web/src/components/admin/refresh-control.tsx +0 -90
- package/template/apps/web/src/components/admin/session-revoke-all-dialog.tsx +0 -79
- package/template/apps/web/src/components/admin/session-revoke-dialog.tsx +0 -74
- package/template/apps/web/src/components/admin/sessions-management-table.tsx +0 -372
- package/template/apps/web/src/components/admin/stat-card.tsx +0 -137
- package/template/apps/web/src/components/admin/user-create-dialog.tsx +0 -152
- package/template/apps/web/src/components/admin/user-delete-dialog.tsx +0 -73
- package/template/apps/web/src/components/admin/user-edit-dialog.tsx +0 -170
- package/template/apps/web/src/components/admin/users-management-table.tsx +0 -285
- package/template/apps/web/src/components/auth/email-verification-banner.tsx +0 -85
- package/template/apps/web/src/components/auth/github-button.tsx +0 -40
- package/template/apps/web/src/components/auth/google-button.tsx +0 -54
- package/template/apps/web/src/components/auth/protected-route.tsx +0 -66
- package/template/apps/web/src/components/auth/redirect-if-authenticated.tsx +0 -31
- package/template/apps/web/src/components/auth/with-auth.tsx +0 -30
- package/template/apps/web/src/components/error/error-card.tsx +0 -47
- package/template/apps/web/src/components/error/forbidden.tsx +0 -25
- package/template/apps/web/src/components/landing/command-block.tsx +0 -64
- package/template/apps/web/src/components/landing/feature-card.tsx +0 -60
- package/template/apps/web/src/components/landing/included-feature-card.tsx +0 -63
- package/template/apps/web/src/components/landing/logo.tsx +0 -41
- package/template/apps/web/src/components/landing/tech-badge.tsx +0 -11
- package/template/apps/web/src/components/layout/auth-nav.tsx +0 -58
- package/template/apps/web/src/components/layout/footer.tsx +0 -3
- package/template/apps/web/src/config/landing-data.ts +0 -152
- package/template/apps/web/src/config/site.ts +0 -5
- package/template/apps/web/src/hooks/api/__tests__/use-users.test.tsx +0 -181
- package/template/apps/web/src/hooks/api/use-admin-sessions.ts +0 -75
- package/template/apps/web/src/hooks/api/use-admin-stats.ts +0 -33
- package/template/apps/web/src/hooks/api/use-sessions.ts +0 -52
- package/template/apps/web/src/hooks/api/use-uploads.ts +0 -156
- package/template/apps/web/src/hooks/api/use-users.ts +0 -149
- package/template/apps/web/src/hooks/use-mobile.ts +0 -21
- package/template/apps/web/src/hooks/use-realtime-metrics.ts +0 -120
- package/template/apps/web/src/lib/__tests__/utils.test.ts +0 -29
- package/template/apps/web/src/lib/api.ts +0 -151
- package/template/apps/web/src/lib/auth.ts +0 -13
- package/template/apps/web/src/lib/env.ts +0 -52
- package/template/apps/web/src/lib/form-utils.ts +0 -11
- package/template/apps/web/src/lib/utils.ts +0 -1
- package/template/apps/web/src/providers.tsx +0 -34
- package/template/apps/web/src/store/atoms.ts +0 -15
- package/template/apps/web/src/test/helpers/test-utils.tsx +0 -44
- package/template/apps/web/src/test/setup.ts +0 -8
- package/template/apps/web/tailwind.config.ts +0 -5
- package/template/apps/web/tsconfig.json +0 -26
- package/template/apps/web/vitest.config.ts +0 -32
- package/template/assets/logo-512.png +0 -0
- package/template/assets/logo.svg +0 -4
- package/template/docker-compose.prod.yml +0 -66
- package/template/docker-compose.yml +0 -36
- package/template/eslint.config.ts +0 -119
- package/template/package.json +0 -77
- package/template/packages/tailwind-config/package.json +0 -9
- package/template/packages/tailwind-config/theme.css +0 -179
- package/template/packages/types/package.json +0 -29
- package/template/packages/types/src/__tests__/schemas.test.ts +0 -255
- package/template/packages/types/src/api-response.ts +0 -53
- package/template/packages/types/src/health-check.ts +0 -11
- package/template/packages/types/src/pagination.ts +0 -41
- package/template/packages/types/src/role.ts +0 -5
- package/template/packages/types/src/session.ts +0 -48
- package/template/packages/types/src/stats.ts +0 -113
- package/template/packages/types/src/upload.ts +0 -51
- package/template/packages/types/src/user.ts +0 -36
- package/template/packages/types/tsconfig.json +0 -5
- package/template/packages/types/vitest.config.ts +0 -21
- package/template/packages/ui/components.json +0 -21
- package/template/packages/ui/package.json +0 -108
- package/template/packages/ui/src/__tests__/button.test.tsx +0 -70
- package/template/packages/ui/src/alert-dialog.tsx +0 -141
- package/template/packages/ui/src/alert.tsx +0 -66
- package/template/packages/ui/src/animated-theme-toggler.tsx +0 -167
- package/template/packages/ui/src/avatar.tsx +0 -53
- package/template/packages/ui/src/badge.tsx +0 -36
- package/template/packages/ui/src/button.tsx +0 -84
- package/template/packages/ui/src/card.tsx +0 -92
- package/template/packages/ui/src/checkbox.tsx +0 -32
- package/template/packages/ui/src/data-table/data-table-column-header.tsx +0 -68
- package/template/packages/ui/src/data-table/data-table-pagination.tsx +0 -99
- package/template/packages/ui/src/data-table/data-table-toolbar.tsx +0 -55
- package/template/packages/ui/src/data-table/data-table-view-options.tsx +0 -63
- package/template/packages/ui/src/data-table/data-table.tsx +0 -167
- package/template/packages/ui/src/dialog.tsx +0 -143
- package/template/packages/ui/src/dropdown-menu.tsx +0 -257
- package/template/packages/ui/src/empty-state.tsx +0 -52
- package/template/packages/ui/src/file-upload-input.tsx +0 -202
- package/template/packages/ui/src/form.tsx +0 -168
- package/template/packages/ui/src/hooks/use-mobile.ts +0 -19
- package/template/packages/ui/src/icons/brand-icons.tsx +0 -16
- package/template/packages/ui/src/input.tsx +0 -21
- package/template/packages/ui/src/label.tsx +0 -24
- package/template/packages/ui/src/lib/utils.ts +0 -6
- package/template/packages/ui/src/password-input.tsx +0 -102
- package/template/packages/ui/src/popover.tsx +0 -48
- package/template/packages/ui/src/radio-group.tsx +0 -45
- package/template/packages/ui/src/scroll-area.tsx +0 -58
- package/template/packages/ui/src/select.tsx +0 -187
- package/template/packages/ui/src/separator.tsx +0 -28
- package/template/packages/ui/src/sheet.tsx +0 -139
- package/template/packages/ui/src/sidebar.tsx +0 -726
- package/template/packages/ui/src/skeleton-variants.tsx +0 -87
- package/template/packages/ui/src/skeleton.tsx +0 -13
- package/template/packages/ui/src/slider.tsx +0 -63
- package/template/packages/ui/src/sonner.tsx +0 -25
- package/template/packages/ui/src/spinner.tsx +0 -16
- package/template/packages/ui/src/switch.tsx +0 -31
- package/template/packages/ui/src/table.tsx +0 -116
- package/template/packages/ui/src/tabs.tsx +0 -66
- package/template/packages/ui/src/textarea.tsx +0 -18
- package/template/packages/ui/src/tooltip.tsx +0 -61
- package/template/packages/ui/src/user-avatar.tsx +0 -97
- package/template/packages/ui/test-config.js +0 -3
- package/template/packages/ui/tsconfig.json +0 -12
- package/template/packages/ui/turbo.json +0 -18
- package/template/packages/ui/vitest.config.ts +0 -17
- package/template/packages/ui/vitest.setup.ts +0 -1
- package/template/packages/utils/package.json +0 -23
- package/template/packages/utils/src/__tests__/utils.test.ts +0 -223
- package/template/packages/utils/src/array.ts +0 -18
- package/template/packages/utils/src/async.ts +0 -3
- package/template/packages/utils/src/date.ts +0 -77
- package/template/packages/utils/src/errors.ts +0 -73
- package/template/packages/utils/src/number.ts +0 -11
- package/template/packages/utils/src/string.ts +0 -13
- package/template/packages/utils/tsconfig.json +0 -5
- package/template/packages/utils/vitest.config.ts +0 -21
- package/template/pnpm-workspace.yaml +0 -4
- package/template/tsconfig.base.json +0 -32
- package/template/turbo.json +0 -133
- package/template/vitest.shared.ts +0 -26
- 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);
|