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,33 +0,0 @@
|
|
|
1
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
-
import fp from 'fastify-plugin';
|
|
3
|
-
|
|
4
|
-
import { LoggerService } from '@/common/logger.service';
|
|
5
|
-
import { loadEnv } from '@/config/env';
|
|
6
|
-
|
|
7
|
-
const env = loadEnv();
|
|
8
|
-
|
|
9
|
-
declare module 'fastify' {
|
|
10
|
-
interface FastifyInstance {
|
|
11
|
-
logger: LoggerService;
|
|
12
|
-
}
|
|
13
|
-
interface FastifyRequest {
|
|
14
|
-
logger: LoggerService;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const loggerPlugin: FastifyPluginAsync = async (app) => {
|
|
19
|
-
const logger = new LoggerService(app.log);
|
|
20
|
-
logger.setContext('FastifyApp');
|
|
21
|
-
|
|
22
|
-
app.decorate('logger', logger);
|
|
23
|
-
|
|
24
|
-
app.addHook('onRequest', async (request) => {
|
|
25
|
-
request.logger = new LoggerService(request.log);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
app.log.info(
|
|
29
|
-
`[+] Logger service configured with verbosity: ${env.LOG_LEVEL}`
|
|
30
|
-
);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export default fp(loggerPlugin);
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import multipart from '@fastify/multipart';
|
|
2
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
3
|
-
import fp from 'fastify-plugin';
|
|
4
|
-
|
|
5
|
-
import { MAX_FILE_SIZE } from '@/utils/file-validation';
|
|
6
|
-
|
|
7
|
-
const multipartPlugin: FastifyPluginAsync = async (app) => {
|
|
8
|
-
await app.register(multipart, {
|
|
9
|
-
limits: {
|
|
10
|
-
fileSize: MAX_FILE_SIZE,
|
|
11
|
-
files: 1, // Allow 1 file per request (can be increased)
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export default fp(multipartPlugin);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import apiReference from '@scalar/fastify-api-reference';
|
|
2
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
3
|
-
import fp from 'fastify-plugin';
|
|
4
|
-
|
|
5
|
-
const scalarPlugin: FastifyPluginAsync = async (app) => {
|
|
6
|
-
await app.register(apiReference, {
|
|
7
|
-
routePrefix: '/docs',
|
|
8
|
-
configuration: {
|
|
9
|
-
theme: 'purple',
|
|
10
|
-
darkMode: true,
|
|
11
|
-
layout: 'modern',
|
|
12
|
-
showSidebar: true,
|
|
13
|
-
searchHotKey: 'k',
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
app.log.info('[+] Scalar docs available at /docs');
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export default fp(scalarPlugin);
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import fastifySchedule from '@fastify/schedule';
|
|
2
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
3
|
-
import fp from 'fastify-plugin';
|
|
4
|
-
import { SimpleIntervalJob, Task } from 'toad-scheduler';
|
|
5
|
-
|
|
6
|
-
const schedulePlugin: FastifyPluginAsync = async (app) => {
|
|
7
|
-
// Register the schedule plugin
|
|
8
|
-
await app.register(fastifySchedule);
|
|
9
|
-
|
|
10
|
-
// Session cleanup task - runs every day at 3 AM (86400 seconds = 24 hours)
|
|
11
|
-
const sessionCleanupTask = new Task('session-cleanup', async () => {
|
|
12
|
-
const logger = app.logger.child('SessionCleanupTask');
|
|
13
|
-
logger.info('Starting expired session cleanup');
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const result = await app.prisma.session.deleteMany({
|
|
17
|
-
where: {
|
|
18
|
-
expiresAt: {
|
|
19
|
-
lt: new Date(),
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
logger.info('Expired sessions cleaned up successfully', {
|
|
25
|
-
deletedCount: result.count,
|
|
26
|
-
});
|
|
27
|
-
} catch (error) {
|
|
28
|
-
logger.error(
|
|
29
|
-
'Failed to cleanup expired sessions',
|
|
30
|
-
error instanceof Error ? error : new Error(String(error))
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Create a job that runs every 24 hours
|
|
36
|
-
const job = new SimpleIntervalJob(
|
|
37
|
-
{ days: 1, runImmediately: false },
|
|
38
|
-
sessionCleanupTask
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
// Add the job to the scheduler
|
|
42
|
-
app.scheduler.addSimpleIntervalJob(job);
|
|
43
|
-
|
|
44
|
-
app.log.info('[+] Scheduled tasks configured');
|
|
45
|
-
|
|
46
|
-
app.addHook('onClose', async (instance) => {
|
|
47
|
-
instance.log.info('[-] Stopping scheduled tasks...');
|
|
48
|
-
instance.scheduler.stop();
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export default fp(schedulePlugin);
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
-
import fp from 'fastify-plugin';
|
|
3
|
-
|
|
4
|
-
import type { Env } from '@/config/env';
|
|
5
|
-
import { loadEnv } from '@/config/env';
|
|
6
|
-
import { AccountsService } from '@/services/accounts.service';
|
|
7
|
-
import { AuthorizationService } from '@/services/authorization.service';
|
|
8
|
-
import { EmailService } from '@/services/email.service';
|
|
9
|
-
import { FileStorageService } from '@/services/file-storage.service';
|
|
10
|
-
import { PasswordService } from '@/services/password.service';
|
|
11
|
-
import { SessionsService } from '@/services/sessions.service';
|
|
12
|
-
import { StatsService } from '@/services/stats.service';
|
|
13
|
-
import { UploadsService } from '@/services/uploads.service';
|
|
14
|
-
import { UsersService } from '@/services/users.service';
|
|
15
|
-
|
|
16
|
-
declare module 'fastify' {
|
|
17
|
-
interface FastifyInstance {
|
|
18
|
-
env: Env;
|
|
19
|
-
authorizationService: AuthorizationService;
|
|
20
|
-
usersService: UsersService;
|
|
21
|
-
sessionsService: SessionsService;
|
|
22
|
-
passwordService: PasswordService;
|
|
23
|
-
emailService: EmailService;
|
|
24
|
-
fileStorageService: FileStorageService;
|
|
25
|
-
uploadsService: UploadsService;
|
|
26
|
-
accountsService: AccountsService;
|
|
27
|
-
statsService: StatsService;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const servicesPlugin: FastifyPluginAsync = async (app) => {
|
|
32
|
-
const env = loadEnv();
|
|
33
|
-
|
|
34
|
-
const authorizationService = new AuthorizationService(app.logger);
|
|
35
|
-
const emailService = new EmailService(env, app.logger, app.prisma);
|
|
36
|
-
const fileStorageService = new FileStorageService(env, app.logger);
|
|
37
|
-
const usersService = new UsersService(
|
|
38
|
-
app.prisma,
|
|
39
|
-
app.logger,
|
|
40
|
-
authorizationService
|
|
41
|
-
);
|
|
42
|
-
const sessionsService = new SessionsService(app.prisma, authorizationService);
|
|
43
|
-
const passwordService = new PasswordService(app.prisma, sessionsService);
|
|
44
|
-
const uploadsService = new UploadsService(
|
|
45
|
-
app.prisma,
|
|
46
|
-
fileStorageService,
|
|
47
|
-
app.logger
|
|
48
|
-
);
|
|
49
|
-
const accountsService = new AccountsService(app.prisma, app.logger);
|
|
50
|
-
const statsService = new StatsService(app.prisma, app.logger);
|
|
51
|
-
|
|
52
|
-
app.decorate('env', env);
|
|
53
|
-
app.decorate('authorizationService', authorizationService);
|
|
54
|
-
app.decorate('emailService', emailService);
|
|
55
|
-
app.decorate('fileStorageService', fileStorageService);
|
|
56
|
-
app.decorate('usersService', usersService);
|
|
57
|
-
app.decorate('sessionsService', sessionsService);
|
|
58
|
-
app.decorate('passwordService', passwordService);
|
|
59
|
-
app.decorate('uploadsService', uploadsService);
|
|
60
|
-
app.decorate('accountsService', accountsService);
|
|
61
|
-
app.decorate('statsService', statsService);
|
|
62
|
-
|
|
63
|
-
app.log.info('[+] Services configured');
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export default fp(servicesPlugin);
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import swagger from '@fastify/swagger';
|
|
2
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
3
|
-
import fp from 'fastify-plugin';
|
|
4
|
-
import {
|
|
5
|
-
jsonSchemaTransform,
|
|
6
|
-
type ZodTypeProvider,
|
|
7
|
-
} from 'fastify-type-provider-zod';
|
|
8
|
-
|
|
9
|
-
import { loadEnv } from '@/config/env';
|
|
10
|
-
|
|
11
|
-
const swaggerPlugin: FastifyPluginAsync = async (app) => {
|
|
12
|
-
const env = loadEnv();
|
|
13
|
-
|
|
14
|
-
await app.withTypeProvider<ZodTypeProvider>().register(swagger, {
|
|
15
|
-
openapi: {
|
|
16
|
-
info: {
|
|
17
|
-
title: 'API',
|
|
18
|
-
description: 'Production-ready TypeScript API built with Fastify',
|
|
19
|
-
version: '1.0.0',
|
|
20
|
-
},
|
|
21
|
-
servers: [
|
|
22
|
-
{
|
|
23
|
-
url: env.API_URL,
|
|
24
|
-
description: `${env.NODE_ENV.charAt(0).toUpperCase() + env.NODE_ENV.slice(1)} server`,
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
components: {
|
|
28
|
-
securitySchemes: {
|
|
29
|
-
cookieAuth: {
|
|
30
|
-
type: 'apiKey',
|
|
31
|
-
in: 'cookie',
|
|
32
|
-
name: 'better-auth.session_token',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
security: [
|
|
37
|
-
{
|
|
38
|
-
cookieAuth: [],
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
tags: [
|
|
42
|
-
{ name: 'Health', description: 'Health check endpoints' },
|
|
43
|
-
{ name: 'Users', description: 'User management endpoints' },
|
|
44
|
-
{
|
|
45
|
-
name: 'Auth',
|
|
46
|
-
description: 'Authentication endpoints (handled by Better Auth)',
|
|
47
|
-
},
|
|
48
|
-
{ name: 'Sessions', description: 'Session management endpoints' },
|
|
49
|
-
{ name: 'Password', description: 'Password management endpoints' },
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
transform: jsonSchemaTransform,
|
|
53
|
-
});
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export default fp(swaggerPlugin);
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
-
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { requireAuth } from '@/hooks/auth';
|
|
6
|
-
|
|
7
|
-
const accountsRoutes: FastifyPluginAsync = async (app) => {
|
|
8
|
-
const server = app.withTypeProvider<ZodTypeProvider>();
|
|
9
|
-
|
|
10
|
-
server.get(
|
|
11
|
-
'/accounts',
|
|
12
|
-
{
|
|
13
|
-
schema: {
|
|
14
|
-
description: 'Get all connected accounts for the authenticated user',
|
|
15
|
-
tags: ['Accounts'],
|
|
16
|
-
response: {
|
|
17
|
-
200: z.object({
|
|
18
|
-
userId: z.string(),
|
|
19
|
-
hasPassword: z.boolean(),
|
|
20
|
-
connectedAccounts: z.array(
|
|
21
|
-
z.object({
|
|
22
|
-
providerId: z.string(),
|
|
23
|
-
accountId: z.string(),
|
|
24
|
-
connectedAt: z.date(),
|
|
25
|
-
scope: z.string().optional(),
|
|
26
|
-
})
|
|
27
|
-
),
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
preHandler: requireAuth,
|
|
32
|
-
},
|
|
33
|
-
async (request) => {
|
|
34
|
-
const userId = request.user!.id;
|
|
35
|
-
return app.accountsService.getUserAccounts(userId);
|
|
36
|
-
}
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
server.delete(
|
|
40
|
-
'/accounts/:providerId',
|
|
41
|
-
{
|
|
42
|
-
schema: {
|
|
43
|
-
description: 'Unlink an OAuth provider from the user account',
|
|
44
|
-
tags: ['Accounts'],
|
|
45
|
-
params: z.object({
|
|
46
|
-
providerId: z.enum(['google', 'github']),
|
|
47
|
-
}),
|
|
48
|
-
response: {
|
|
49
|
-
200: z.object({
|
|
50
|
-
success: z.boolean(),
|
|
51
|
-
}),
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
preHandler: requireAuth,
|
|
55
|
-
},
|
|
56
|
-
async (request) => {
|
|
57
|
-
const userId = request.user!.id;
|
|
58
|
-
const { providerId } = request.params;
|
|
59
|
-
const result = await app.accountsService.unlinkAccount(
|
|
60
|
-
userId,
|
|
61
|
-
providerId
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
server.get(
|
|
69
|
-
'/accounts/can-change-password',
|
|
70
|
-
{
|
|
71
|
-
schema: {
|
|
72
|
-
description:
|
|
73
|
-
'Check if user can change password (has password set via credential provider)',
|
|
74
|
-
tags: ['Accounts'],
|
|
75
|
-
response: {
|
|
76
|
-
200: z.object({
|
|
77
|
-
canChangePassword: z.boolean(),
|
|
78
|
-
}),
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
preHandler: requireAuth,
|
|
82
|
-
},
|
|
83
|
-
async (request) => {
|
|
84
|
-
const userId = request.user!.id;
|
|
85
|
-
const canChange = await app.accountsService.canChangePassword(userId);
|
|
86
|
-
return { canChangePassword: canChange };
|
|
87
|
-
}
|
|
88
|
-
);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export default accountsRoutes;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
QuerySessionsSchema,
|
|
3
|
-
RevokeSessionParamsSchema,
|
|
4
|
-
RevokeUserSessionsParamsSchema,
|
|
5
|
-
} from '@repo/packages-types/session';
|
|
6
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
7
|
-
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
8
|
-
import type { z } from 'zod';
|
|
9
|
-
|
|
10
|
-
import { requireAuth, requireRole } from '@/hooks/auth';
|
|
11
|
-
|
|
12
|
-
type RevokeSessionParams = z.infer<typeof RevokeSessionParamsSchema>;
|
|
13
|
-
type RevokeUserSessionsParams = z.infer<typeof RevokeUserSessionsParamsSchema>;
|
|
14
|
-
|
|
15
|
-
const adminSessionsRoutes: FastifyPluginAsync = async (app) => {
|
|
16
|
-
const server = app.withTypeProvider<ZodTypeProvider>();
|
|
17
|
-
|
|
18
|
-
server.get(
|
|
19
|
-
'/admin/sessions',
|
|
20
|
-
{
|
|
21
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
22
|
-
schema: {
|
|
23
|
-
querystring: QuerySessionsSchema,
|
|
24
|
-
description: 'Get paginated sessions with filtering (Admin only)',
|
|
25
|
-
tags: ['Admin'],
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
async (request) => {
|
|
29
|
-
return app.sessionsService.getAdminSessions(request.query);
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
server.get(
|
|
34
|
-
'/admin/sessions/stats',
|
|
35
|
-
{
|
|
36
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
37
|
-
schema: {
|
|
38
|
-
description: 'Get session statistics (Admin only)',
|
|
39
|
-
tags: ['Admin'],
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
async () => {
|
|
43
|
-
return app.sessionsService.getSessionStats();
|
|
44
|
-
}
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
server.delete<{ Params: RevokeSessionParams }>(
|
|
48
|
-
'/admin/sessions/:sessionId',
|
|
49
|
-
{
|
|
50
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
51
|
-
schema: {
|
|
52
|
-
params: RevokeSessionParamsSchema,
|
|
53
|
-
description: 'Force revoke a session (Admin only)',
|
|
54
|
-
tags: ['Admin'],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
async (request) => {
|
|
58
|
-
const { sessionId } = request.params;
|
|
59
|
-
await app.sessionsService.adminRevokeSession(
|
|
60
|
-
request.user!.id,
|
|
61
|
-
request.user!.role as 'user' | 'admin' | 'super_admin',
|
|
62
|
-
sessionId
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
return { message: 'Session revoked successfully' };
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
server.delete<{ Params: RevokeUserSessionsParams }>(
|
|
70
|
-
'/admin/sessions/user/:userId',
|
|
71
|
-
{
|
|
72
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
73
|
-
schema: {
|
|
74
|
-
params: RevokeUserSessionsParamsSchema,
|
|
75
|
-
description: 'Revoke all sessions for a user (Admin only)',
|
|
76
|
-
tags: ['Admin'],
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
async (request) => {
|
|
80
|
-
const { userId } = request.params;
|
|
81
|
-
const count = await app.sessionsService.adminRevokeUserSessions(
|
|
82
|
-
request.user!.id,
|
|
83
|
-
request.user!.role as 'user' | 'admin' | 'super_admin',
|
|
84
|
-
userId
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
return { message: `Revoked ${count} session(s) for user` };
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export default adminSessionsRoutes;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
-
|
|
3
|
-
import { requireAuth, requireRole } from '@/hooks/auth';
|
|
4
|
-
import { metricsService } from '@/services/metrics.service';
|
|
5
|
-
|
|
6
|
-
const metricsRoutes: FastifyPluginAsync = async (app) => {
|
|
7
|
-
app.get(
|
|
8
|
-
'/admin/metrics/stream',
|
|
9
|
-
{
|
|
10
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
11
|
-
schema: {
|
|
12
|
-
description: 'Stream real-time system metrics via SSE (Admin only)',
|
|
13
|
-
tags: ['Admin'],
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
async (request, reply) => {
|
|
17
|
-
reply.raw.writeHead(200, {
|
|
18
|
-
'Content-Type': 'text/event-stream',
|
|
19
|
-
'Cache-Control': 'no-cache',
|
|
20
|
-
Connection: 'keep-alive',
|
|
21
|
-
'Access-Control-Allow-Origin': app.env.FRONTEND_URL,
|
|
22
|
-
'Access-Control-Allow-Credentials': 'true',
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const history = metricsService.getHistory();
|
|
26
|
-
if (history.length > 0) {
|
|
27
|
-
reply.raw.write(`event: history\n`);
|
|
28
|
-
reply.raw.write(`data: ${JSON.stringify(history)}\n\n`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const unsubscribe = metricsService.subscribe((metrics) => {
|
|
32
|
-
if (!reply.raw.destroyed) {
|
|
33
|
-
reply.raw.write(`event: metrics\n`);
|
|
34
|
-
reply.raw.write(`data: ${JSON.stringify(metrics)}\n\n`);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const heartbeatInterval = setInterval(() => {
|
|
39
|
-
if (!reply.raw.destroyed) {
|
|
40
|
-
reply.raw.write(`:heartbeat\n\n`);
|
|
41
|
-
}
|
|
42
|
-
}, 15000);
|
|
43
|
-
|
|
44
|
-
request.raw.on('close', () => {
|
|
45
|
-
unsubscribe();
|
|
46
|
-
clearInterval(heartbeatInterval);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return reply;
|
|
50
|
-
}
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
app.get(
|
|
54
|
-
'/admin/metrics/snapshot',
|
|
55
|
-
{
|
|
56
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
57
|
-
schema: {
|
|
58
|
-
description: 'Get current metrics snapshot (Admin only)',
|
|
59
|
-
tags: ['Admin'],
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
async () => {
|
|
63
|
-
return {
|
|
64
|
-
current: metricsService.getLatest(),
|
|
65
|
-
history: metricsService.getHistory(),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export default metricsRoutes;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from '@repo/packages-utils/errors';
|
|
2
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { requireAuth } from '@/hooks/auth';
|
|
6
|
-
|
|
7
|
-
const ChangePasswordSchema = z.object({
|
|
8
|
-
currentPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
|
9
|
-
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const passwordRoutes: FastifyPluginAsync = async (app) => {
|
|
13
|
-
app.post<{
|
|
14
|
-
Body: z.infer<typeof ChangePasswordSchema>;
|
|
15
|
-
}>(
|
|
16
|
-
'/password/change',
|
|
17
|
-
{
|
|
18
|
-
preHandler: requireAuth,
|
|
19
|
-
schema: {
|
|
20
|
-
body: ChangePasswordSchema,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
async (request) => {
|
|
24
|
-
const { currentPassword, newPassword } = request.body;
|
|
25
|
-
|
|
26
|
-
if (currentPassword === newPassword) {
|
|
27
|
-
throw new ValidationError(
|
|
28
|
-
'New password must be different from current password'
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
await app.passwordService.changePassword(
|
|
33
|
-
request.user!.id,
|
|
34
|
-
currentPassword,
|
|
35
|
-
newPassword
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
message:
|
|
40
|
-
'Password changed successfully. All other sessions have been revoked.',
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export default passwordRoutes;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
-
|
|
3
|
-
import { requireAuth } from '@/hooks/auth';
|
|
4
|
-
|
|
5
|
-
const sessionsRoutes: FastifyPluginAsync = async (app) => {
|
|
6
|
-
app.get(
|
|
7
|
-
'/sessions',
|
|
8
|
-
{
|
|
9
|
-
preHandler: requireAuth,
|
|
10
|
-
},
|
|
11
|
-
async (request) => {
|
|
12
|
-
const sessions = await app.sessionsService.getUserSessions(
|
|
13
|
-
request.user!.id
|
|
14
|
-
);
|
|
15
|
-
return { data: sessions };
|
|
16
|
-
}
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
app.delete<{
|
|
20
|
-
Params: { sessionId: string };
|
|
21
|
-
}>(
|
|
22
|
-
'/sessions/:sessionId',
|
|
23
|
-
{
|
|
24
|
-
preHandler: requireAuth,
|
|
25
|
-
},
|
|
26
|
-
async (request) => {
|
|
27
|
-
await app.sessionsService.revokeSession(
|
|
28
|
-
request.user!.id,
|
|
29
|
-
request.params.sessionId
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
return { message: 'Session revoked successfully' };
|
|
33
|
-
}
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
app.delete(
|
|
37
|
-
'/sessions',
|
|
38
|
-
{
|
|
39
|
-
preHandler: requireAuth,
|
|
40
|
-
},
|
|
41
|
-
async (request) => {
|
|
42
|
-
const currentSessionId = request.user?.session?.id;
|
|
43
|
-
await app.sessionsService.revokeAllSessions(
|
|
44
|
-
request.user!.id,
|
|
45
|
-
currentSessionId
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
return { message: 'All other sessions revoked successfully' };
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export default sessionsRoutes;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
-
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
3
|
-
|
|
4
|
-
import { requireAuth, requireRole } from '@/hooks/auth';
|
|
5
|
-
|
|
6
|
-
const statsRoutes: FastifyPluginAsync = async (app) => {
|
|
7
|
-
const server = app.withTypeProvider<ZodTypeProvider>();
|
|
8
|
-
|
|
9
|
-
server.get(
|
|
10
|
-
'/admin/stats',
|
|
11
|
-
{
|
|
12
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
13
|
-
schema: {
|
|
14
|
-
description: 'Get comprehensive system statistics (Admin only)',
|
|
15
|
-
tags: ['Admin'],
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
async () => {
|
|
19
|
-
return app.statsService.getSystemStats();
|
|
20
|
-
}
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
server.get(
|
|
24
|
-
'/admin/stats/health',
|
|
25
|
-
{
|
|
26
|
-
preHandler: [requireAuth, requireRole(['admin', 'super_admin'])],
|
|
27
|
-
schema: {
|
|
28
|
-
description: 'Get system health check with DB latency (Admin only)',
|
|
29
|
-
tags: ['Admin'],
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
async () => {
|
|
33
|
-
return app.statsService.getHealthCheck();
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export default statsRoutes;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import fastifyStatic from '@fastify/static';
|
|
4
|
-
import type { FastifyPluginAsync } from 'fastify';
|
|
5
|
-
|
|
6
|
-
const uploadsServeRoutes: FastifyPluginAsync = async (app) => {
|
|
7
|
-
const storageType = app.fileStorageService.getStorageType();
|
|
8
|
-
|
|
9
|
-
// Only serve local files if using local storage
|
|
10
|
-
if (storageType === 'local') {
|
|
11
|
-
const uploadsDir = path.join(process.cwd(), 'public', 'uploads');
|
|
12
|
-
|
|
13
|
-
await app.register(fastifyStatic, {
|
|
14
|
-
root: uploadsDir,
|
|
15
|
-
prefix: '/uploads/files/',
|
|
16
|
-
decorateReply: false,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
app.log.info('[+] Local file serving configured at /uploads/files/');
|
|
20
|
-
} else {
|
|
21
|
-
app.log.info(
|
|
22
|
-
`[+] Using ${storageType} storage - local file serving disabled`
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export default uploadsServeRoutes;
|