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,116 +0,0 @@
|
|
|
1
|
-
// This is your Prisma schema file,
|
|
2
|
-
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
-
|
|
4
|
-
generator client {
|
|
5
|
-
provider = "prisma-client"
|
|
6
|
-
output = "../src/generated/client"
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
datasource db {
|
|
10
|
-
provider = "postgresql"
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Role enum for user permissions
|
|
14
|
-
enum Role {
|
|
15
|
-
user
|
|
16
|
-
admin
|
|
17
|
-
super_admin
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// User model with better-auth support
|
|
21
|
-
model User {
|
|
22
|
-
id String @id @default(cuid())
|
|
23
|
-
email String @unique
|
|
24
|
-
emailVerified Boolean @default(false)
|
|
25
|
-
name String?
|
|
26
|
-
image String?
|
|
27
|
-
role Role @default(user)
|
|
28
|
-
banned Boolean @default(false)
|
|
29
|
-
banReason String?
|
|
30
|
-
banExpires DateTime?
|
|
31
|
-
createdAt DateTime @default(now())
|
|
32
|
-
updatedAt DateTime @updatedAt
|
|
33
|
-
|
|
34
|
-
sessions Session[]
|
|
35
|
-
accounts Account[]
|
|
36
|
-
uploads Upload[]
|
|
37
|
-
|
|
38
|
-
@@map("users")
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Session model for better-auth
|
|
42
|
-
model Session {
|
|
43
|
-
id String @id @default(cuid())
|
|
44
|
-
userId String
|
|
45
|
-
expiresAt DateTime
|
|
46
|
-
token String @unique
|
|
47
|
-
ipAddress String?
|
|
48
|
-
userAgent String?
|
|
49
|
-
createdAt DateTime @default(now())
|
|
50
|
-
updatedAt DateTime @updatedAt
|
|
51
|
-
|
|
52
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
53
|
-
|
|
54
|
-
@@index([userId])
|
|
55
|
-
@@index([expiresAt])
|
|
56
|
-
@@map("sessions")
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Account model for OAuth providers
|
|
60
|
-
// IMPORTANT: This schema MUST match Better Auth's expected fields exactly
|
|
61
|
-
// Do NOT modify field names without checking Better Auth documentation
|
|
62
|
-
// https://www.better-auth.com/docs/concepts/database#account-model
|
|
63
|
-
model Account {
|
|
64
|
-
id String @id @default(cuid())
|
|
65
|
-
userId String
|
|
66
|
-
accountId String
|
|
67
|
-
providerId String
|
|
68
|
-
accessToken String?
|
|
69
|
-
refreshToken String?
|
|
70
|
-
idToken String?
|
|
71
|
-
accessTokenExpiresAt DateTime? // OAuth access token expiration
|
|
72
|
-
refreshTokenExpiresAt DateTime? // OAuth refresh token expiration
|
|
73
|
-
scope String? // OAuth scopes granted
|
|
74
|
-
password String? // For credential provider (email/password)
|
|
75
|
-
createdAt DateTime @default(now())
|
|
76
|
-
updatedAt DateTime @updatedAt
|
|
77
|
-
|
|
78
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
79
|
-
|
|
80
|
-
@@unique([providerId, accountId])
|
|
81
|
-
@@index([userId])
|
|
82
|
-
@@map("accounts")
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Verification model for email verification and password reset
|
|
86
|
-
model Verification {
|
|
87
|
-
id String @id @default(cuid())
|
|
88
|
-
identifier String
|
|
89
|
-
value String
|
|
90
|
-
expiresAt DateTime
|
|
91
|
-
createdAt DateTime @default(now())
|
|
92
|
-
updatedAt DateTime @updatedAt
|
|
93
|
-
|
|
94
|
-
@@unique([identifier, value])
|
|
95
|
-
@@map("verifications")
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Upload model for file tracking
|
|
99
|
-
model Upload {
|
|
100
|
-
id String @id @default(cuid())
|
|
101
|
-
filename String @unique // stored filename (unique)
|
|
102
|
-
originalName String // user's original filename
|
|
103
|
-
mimeType String
|
|
104
|
-
size Int // bytes
|
|
105
|
-
url String // public URL
|
|
106
|
-
userId String
|
|
107
|
-
createdAt DateTime @default(now())
|
|
108
|
-
updatedAt DateTime @updatedAt
|
|
109
|
-
|
|
110
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
111
|
-
|
|
112
|
-
@@index([userId])
|
|
113
|
-
@@index([createdAt])
|
|
114
|
-
@@map("uploads")
|
|
115
|
-
}
|
|
116
|
-
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import 'dotenv-flow/config';
|
|
2
|
-
|
|
3
|
-
import { PrismaPg } from '@prisma/adapter-pg';
|
|
4
|
-
import { betterAuth } from 'better-auth';
|
|
5
|
-
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
|
6
|
-
import { admin } from 'better-auth/plugins';
|
|
7
|
-
import { Pool } from 'pg';
|
|
8
|
-
|
|
9
|
-
import { PrismaClient, Role } from '../src/generated/client/client.js';
|
|
10
|
-
|
|
11
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
12
|
-
const adapter = new PrismaPg(pool);
|
|
13
|
-
const prisma = new PrismaClient({ adapter });
|
|
14
|
-
|
|
15
|
-
const auth = betterAuth({
|
|
16
|
-
database: prismaAdapter(prisma, {
|
|
17
|
-
provider: 'postgresql',
|
|
18
|
-
}),
|
|
19
|
-
baseURL: 'http://localhost:8080',
|
|
20
|
-
secret: 'temp-seed-secret',
|
|
21
|
-
emailAndPassword: {
|
|
22
|
-
enabled: true,
|
|
23
|
-
},
|
|
24
|
-
plugins: [
|
|
25
|
-
admin({
|
|
26
|
-
defaultRole: 'user',
|
|
27
|
-
adminRoles: ['admin', 'super_admin'],
|
|
28
|
-
}),
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
async function main() {
|
|
33
|
-
console.log('🌱 Seeding database...');
|
|
34
|
-
|
|
35
|
-
// SAFETY: Block seeding in production
|
|
36
|
-
if (process.env.NODE_ENV === 'production') {
|
|
37
|
-
console.error('❌ SAFETY ERROR: Seed script is blocked in production!');
|
|
38
|
-
console.error(
|
|
39
|
-
' Test accounts with weak passwords should NEVER be created in production.'
|
|
40
|
-
);
|
|
41
|
-
console.error(
|
|
42
|
-
' If you need to seed production data, create a separate seed-prod.ts file.'
|
|
43
|
-
);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
console.log(`📍 Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
48
|
-
|
|
49
|
-
// Delete all existing test accounts first
|
|
50
|
-
await prisma.account.deleteMany({
|
|
51
|
-
where: {
|
|
52
|
-
providerId: 'credential',
|
|
53
|
-
accountId: {
|
|
54
|
-
in: [
|
|
55
|
-
'admin@example.com',
|
|
56
|
-
'alice@example.com',
|
|
57
|
-
'bob@example.com',
|
|
58
|
-
'charlie@example.com',
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
await prisma.user.deleteMany({
|
|
65
|
-
where: {
|
|
66
|
-
email: {
|
|
67
|
-
in: [
|
|
68
|
-
'admin@example.com',
|
|
69
|
-
'alice@example.com',
|
|
70
|
-
'bob@example.com',
|
|
71
|
-
'charlie@example.com',
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
console.log('🗑️ Cleaned up existing test accounts');
|
|
78
|
-
|
|
79
|
-
const usersData = [
|
|
80
|
-
{
|
|
81
|
-
email: 'admin@example.com',
|
|
82
|
-
name: 'Super Admin',
|
|
83
|
-
password: 'Admin@123456',
|
|
84
|
-
role: Role.super_admin,
|
|
85
|
-
emailVerified: true,
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
email: 'alice@example.com',
|
|
89
|
-
name: 'Alice Johnson',
|
|
90
|
-
password: 'Alice@123456',
|
|
91
|
-
role: Role.user,
|
|
92
|
-
emailVerified: false,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
email: 'bob@example.com',
|
|
96
|
-
name: 'Bob Smith',
|
|
97
|
-
password: 'Bob@123456',
|
|
98
|
-
role: Role.admin,
|
|
99
|
-
emailVerified: false,
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
email: 'charlie@example.com',
|
|
103
|
-
name: 'Charlie Davis',
|
|
104
|
-
password: 'Charlie@123456',
|
|
105
|
-
role: Role.user,
|
|
106
|
-
emailVerified: false,
|
|
107
|
-
},
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
for (const userData of usersData) {
|
|
111
|
-
// Create user with Better Auth (this handles password hashing correctly)
|
|
112
|
-
const response = await auth.api.signUpEmail({
|
|
113
|
-
body: {
|
|
114
|
-
email: userData.email,
|
|
115
|
-
password: userData.password,
|
|
116
|
-
name: userData.name,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (!response || !response.user) {
|
|
121
|
-
console.error(`❌ Failed to create user: ${userData.email}`);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Update user role and email verification status
|
|
126
|
-
await prisma.user.update({
|
|
127
|
-
where: { id: response.user.id },
|
|
128
|
-
data: {
|
|
129
|
-
role: userData.role,
|
|
130
|
-
emailVerified: userData.emailVerified,
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
console.log(
|
|
135
|
-
`✅ Created: ${userData.email} (${userData.role}) - emailVerified: ${userData.emailVerified}`
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
console.log('');
|
|
140
|
-
console.log('✅ Database seeded successfully!');
|
|
141
|
-
console.log('');
|
|
142
|
-
console.log('Test accounts:');
|
|
143
|
-
console.log(' admin@example.com / Admin@123456 (super_admin)');
|
|
144
|
-
console.log(' alice@example.com / Alice@123456 (user)');
|
|
145
|
-
console.log(' bob@example.com / Bob@123456 (admin)');
|
|
146
|
-
console.log(' charlie@example.com / Charlie@123456 (user)');
|
|
147
|
-
console.log('');
|
|
148
|
-
console.log('⚠️ IMPORTANT: Change passwords after first login!');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
main()
|
|
152
|
-
.catch((e) => {
|
|
153
|
-
console.error('❌ Seed failed:', e);
|
|
154
|
-
process.exit(1);
|
|
155
|
-
})
|
|
156
|
-
.finally(async () => {
|
|
157
|
-
await prisma.$disconnect();
|
|
158
|
-
await pool.end();
|
|
159
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import 'dotenv-flow/config';
|
|
2
|
-
|
|
3
|
-
import { defineConfig, env } from 'prisma/config';
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
schema: 'prisma/schema.prisma',
|
|
7
|
-
migrations: {
|
|
8
|
-
path: 'prisma/migrations',
|
|
9
|
-
seed: 'tsx prisma/seed.ts',
|
|
10
|
-
},
|
|
11
|
-
datasource: {
|
|
12
|
-
url: env('DATABASE_URL'),
|
|
13
|
-
},
|
|
14
|
-
});
|
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import cookie from '@fastify/cookie';
|
|
2
|
-
import cors from '@fastify/cors';
|
|
3
|
-
import formbody from '@fastify/formbody';
|
|
4
|
-
import helmet from '@fastify/helmet';
|
|
5
|
-
import rateLimit from '@fastify/rate-limit';
|
|
6
|
-
import { AppError } from '@repo/packages-utils/errors';
|
|
7
|
-
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
|
|
8
|
-
import Fastify from 'fastify';
|
|
9
|
-
import {
|
|
10
|
-
serializerCompiler,
|
|
11
|
-
validatorCompiler,
|
|
12
|
-
type ZodTypeProvider,
|
|
13
|
-
} from 'fastify-type-provider-zod';
|
|
14
|
-
|
|
15
|
-
import { loadEnv } from '@/config/env';
|
|
16
|
-
import type { RateLimitRole } from '@/config/rate-limit';
|
|
17
|
-
import { RATE_LIMIT_CONFIG } from '@/config/rate-limit';
|
|
18
|
-
import { metricsService } from '@/services/metrics.service';
|
|
19
|
-
|
|
20
|
-
const env = loadEnv();
|
|
21
|
-
|
|
22
|
-
export const app = Fastify({
|
|
23
|
-
logger: {
|
|
24
|
-
level: 'trace',
|
|
25
|
-
formatters: {
|
|
26
|
-
level: (label) => ({ level: label }),
|
|
27
|
-
},
|
|
28
|
-
transport:
|
|
29
|
-
env.NODE_ENV === 'development'
|
|
30
|
-
? {
|
|
31
|
-
target: 'pino-pretty',
|
|
32
|
-
options: {
|
|
33
|
-
colorize: true,
|
|
34
|
-
ignore: 'pid,hostname',
|
|
35
|
-
singleLine: false,
|
|
36
|
-
translateTime: 'HH:MM:ss',
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
: undefined,
|
|
40
|
-
},
|
|
41
|
-
disableRequestLogging: true,
|
|
42
|
-
requestIdHeader: 'x-request-id',
|
|
43
|
-
genReqId: () => `req-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
44
|
-
bodyLimit: 1048576,
|
|
45
|
-
routerOptions: {
|
|
46
|
-
ignoreTrailingSlash: true,
|
|
47
|
-
},
|
|
48
|
-
onProtoPoisoning: 'error',
|
|
49
|
-
onConstructorPoisoning: 'error',
|
|
50
|
-
}).withTypeProvider<ZodTypeProvider>();
|
|
51
|
-
|
|
52
|
-
app.setValidatorCompiler(validatorCompiler);
|
|
53
|
-
app.setSerializerCompiler(serializerCompiler);
|
|
54
|
-
|
|
55
|
-
await app.register(helmet, {
|
|
56
|
-
contentSecurityPolicy: {
|
|
57
|
-
directives: {
|
|
58
|
-
defaultSrc: ["'self'"],
|
|
59
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
60
|
-
scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
61
|
-
imgSrc: ["'self'", 'data:', 'https:'],
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
await app.register(cors, {
|
|
67
|
-
origin: env.FRONTEND_URL,
|
|
68
|
-
credentials: true,
|
|
69
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
70
|
-
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
|
|
71
|
-
exposedHeaders: ['X-Request-ID'],
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// @ts-expect-error - Known issue with @fastify/rate-limit type definitions
|
|
75
|
-
await app.register(rateLimit, {
|
|
76
|
-
global: true,
|
|
77
|
-
max: async (request: FastifyRequest) => {
|
|
78
|
-
const session = await request.server.auth.api
|
|
79
|
-
.getSession({
|
|
80
|
-
headers: request.headers as unknown as Headers,
|
|
81
|
-
})
|
|
82
|
-
.catch(() => null);
|
|
83
|
-
|
|
84
|
-
if (!session?.user) {
|
|
85
|
-
return RATE_LIMIT_CONFIG.anonymous.max;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const userWithRole = session.user as typeof session.user & {
|
|
89
|
-
role?: string;
|
|
90
|
-
};
|
|
91
|
-
const role = (userWithRole.role || 'user') as RateLimitRole;
|
|
92
|
-
|
|
93
|
-
return RATE_LIMIT_CONFIG[role]?.max || RATE_LIMIT_CONFIG.user.max;
|
|
94
|
-
},
|
|
95
|
-
timeWindow: 60 * 1000,
|
|
96
|
-
keyGenerator: async (request: FastifyRequest) => {
|
|
97
|
-
const session = await request.server.auth.api
|
|
98
|
-
.getSession({
|
|
99
|
-
headers: request.headers as unknown as Headers,
|
|
100
|
-
})
|
|
101
|
-
.catch(() => null);
|
|
102
|
-
|
|
103
|
-
if (session?.user?.id) {
|
|
104
|
-
return `user:${session.user.id}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return `ip:${request.ip}`;
|
|
108
|
-
},
|
|
109
|
-
addHeadersOnExceeding: {
|
|
110
|
-
'X-RateLimit-Limit': true,
|
|
111
|
-
'X-RateLimit-Remaining': true,
|
|
112
|
-
'X-RateLimit-Reset': true,
|
|
113
|
-
},
|
|
114
|
-
addHeaders: {
|
|
115
|
-
'X-RateLimit-Limit': true,
|
|
116
|
-
'X-RateLimit-Remaining': true,
|
|
117
|
-
'X-RateLimit-Reset': true,
|
|
118
|
-
},
|
|
119
|
-
errorResponseBuilder: (request: FastifyRequest) => ({
|
|
120
|
-
statusCode: 429,
|
|
121
|
-
error: 'Too Many Requests',
|
|
122
|
-
message: 'Rate limit exceeded. Please try again later.',
|
|
123
|
-
}),
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
await app.register(cookie, {
|
|
127
|
-
secret: env.COOKIE_SECRET,
|
|
128
|
-
parseOptions: {},
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
await app.register(formbody);
|
|
132
|
-
|
|
133
|
-
const { default: multipartPlugin } = await import('@/plugins/multipart.js');
|
|
134
|
-
await app.register(multipartPlugin);
|
|
135
|
-
|
|
136
|
-
const { default: loggerPlugin } = await import('@/plugins/logger.js');
|
|
137
|
-
await app.register(loggerPlugin);
|
|
138
|
-
|
|
139
|
-
const { default: databasePlugin } = await import('@/plugins/database.js');
|
|
140
|
-
await app.register(databasePlugin);
|
|
141
|
-
|
|
142
|
-
const { default: servicesPlugin } = await import('@/plugins/services.js');
|
|
143
|
-
await app.register(servicesPlugin);
|
|
144
|
-
|
|
145
|
-
const { default: authPlugin } = await import('@/plugins/auth.js');
|
|
146
|
-
await app.register(authPlugin);
|
|
147
|
-
|
|
148
|
-
const { default: swaggerPlugin } = await import('@/plugins/swagger.js');
|
|
149
|
-
await app.register(swaggerPlugin);
|
|
150
|
-
|
|
151
|
-
const { default: scalarPlugin } = await import('@/plugins/scalar.js');
|
|
152
|
-
await app.register(scalarPlugin);
|
|
153
|
-
|
|
154
|
-
const { default: schedulePlugin } = await import('@/plugins/schedule.js');
|
|
155
|
-
await app.register(schedulePlugin);
|
|
156
|
-
|
|
157
|
-
const errorHandler = (
|
|
158
|
-
error: FastifyError,
|
|
159
|
-
request: FastifyRequest,
|
|
160
|
-
reply: FastifyReply
|
|
161
|
-
): void => {
|
|
162
|
-
request.log.error(
|
|
163
|
-
{
|
|
164
|
-
err: error,
|
|
165
|
-
reqId: request.id,
|
|
166
|
-
url: request.url,
|
|
167
|
-
method: request.method,
|
|
168
|
-
},
|
|
169
|
-
'Request error'
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
if (error instanceof AppError) {
|
|
173
|
-
void reply.status(error.statusCode).send({
|
|
174
|
-
error: {
|
|
175
|
-
message: error.message,
|
|
176
|
-
code: error.code,
|
|
177
|
-
...(error.details && { details: error.details }),
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (error.validation) {
|
|
184
|
-
void reply.status(400).send({
|
|
185
|
-
error: {
|
|
186
|
-
message: 'Validation failed',
|
|
187
|
-
code: 'VALIDATION_ERROR',
|
|
188
|
-
details: error.validation,
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const isProduction = env.NODE_ENV === 'production';
|
|
195
|
-
const statusCode = error.statusCode || 500;
|
|
196
|
-
|
|
197
|
-
void reply.status(statusCode).send({
|
|
198
|
-
error: {
|
|
199
|
-
message:
|
|
200
|
-
isProduction && statusCode === 500
|
|
201
|
-
? 'Internal server error'
|
|
202
|
-
: error.message || 'An error occurred',
|
|
203
|
-
code: 'INTERNAL_ERROR',
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
app.setErrorHandler(errorHandler);
|
|
209
|
-
|
|
210
|
-
app.addHook('onRequest', async (request) => {
|
|
211
|
-
if (env.LOG_LEVEL === 'detailed' || env.LOG_LEVEL === 'verbose') {
|
|
212
|
-
request.log = request.log.child({ reqId: request.id });
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
app.addHook('onResponse', async (request, reply) => {
|
|
217
|
-
try {
|
|
218
|
-
const responseTime = reply.elapsedTime;
|
|
219
|
-
metricsService.recordRequest(responseTime, reply.statusCode);
|
|
220
|
-
|
|
221
|
-
const statusCode = reply.statusCode;
|
|
222
|
-
const isError = statusCode >= 400;
|
|
223
|
-
const logMessage = `${request.method} ${request.url} → ${statusCode} (${responseTime.toFixed(2)}ms)`;
|
|
224
|
-
|
|
225
|
-
switch (env.LOG_LEVEL) {
|
|
226
|
-
case 'minimal':
|
|
227
|
-
if (isError) {
|
|
228
|
-
request.log.error(
|
|
229
|
-
{
|
|
230
|
-
method: request.method,
|
|
231
|
-
url: request.url,
|
|
232
|
-
statusCode,
|
|
233
|
-
responseTime: `${responseTime.toFixed(2)}ms`,
|
|
234
|
-
},
|
|
235
|
-
logMessage
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
|
|
240
|
-
case 'normal':
|
|
241
|
-
if (isError) {
|
|
242
|
-
request.log.error(logMessage);
|
|
243
|
-
} else {
|
|
244
|
-
request.log.info(logMessage);
|
|
245
|
-
}
|
|
246
|
-
break;
|
|
247
|
-
|
|
248
|
-
case 'detailed':
|
|
249
|
-
if (isError) {
|
|
250
|
-
request.log.error(
|
|
251
|
-
{
|
|
252
|
-
method: request.method,
|
|
253
|
-
url: request.url,
|
|
254
|
-
statusCode,
|
|
255
|
-
responseTime: `${responseTime.toFixed(2)}ms`,
|
|
256
|
-
ip: request.ip,
|
|
257
|
-
userAgent: request.headers['user-agent'],
|
|
258
|
-
},
|
|
259
|
-
logMessage
|
|
260
|
-
);
|
|
261
|
-
} else {
|
|
262
|
-
request.log.info(
|
|
263
|
-
{
|
|
264
|
-
method: request.method,
|
|
265
|
-
url: request.url,
|
|
266
|
-
statusCode,
|
|
267
|
-
responseTime: `${responseTime.toFixed(2)}ms`,
|
|
268
|
-
ip: request.ip,
|
|
269
|
-
userAgent: request.headers['user-agent'],
|
|
270
|
-
},
|
|
271
|
-
logMessage
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
break;
|
|
275
|
-
|
|
276
|
-
case 'verbose':
|
|
277
|
-
if (isError) {
|
|
278
|
-
request.log.error(
|
|
279
|
-
{
|
|
280
|
-
method: request.method,
|
|
281
|
-
url: request.url,
|
|
282
|
-
statusCode,
|
|
283
|
-
responseTime: `${responseTime.toFixed(2)}ms`,
|
|
284
|
-
ip: request.ip,
|
|
285
|
-
userAgent: request.headers['user-agent'],
|
|
286
|
-
req: {
|
|
287
|
-
params: request.params,
|
|
288
|
-
query: request.query,
|
|
289
|
-
headers: request.headers,
|
|
290
|
-
},
|
|
291
|
-
res: {
|
|
292
|
-
headers: reply.getHeaders(),
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
logMessage
|
|
296
|
-
);
|
|
297
|
-
} else {
|
|
298
|
-
request.log.info(
|
|
299
|
-
{
|
|
300
|
-
method: request.method,
|
|
301
|
-
url: request.url,
|
|
302
|
-
statusCode,
|
|
303
|
-
responseTime: `${responseTime.toFixed(2)}ms`,
|
|
304
|
-
ip: request.ip,
|
|
305
|
-
userAgent: request.headers['user-agent'],
|
|
306
|
-
req: {
|
|
307
|
-
params: request.params,
|
|
308
|
-
query: request.query,
|
|
309
|
-
headers: request.headers,
|
|
310
|
-
},
|
|
311
|
-
res: {
|
|
312
|
-
headers: reply.getHeaders(),
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
logMessage
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error('[onResponse hook error]:', error);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
app.get('/health', async (request, reply) => {
|
|
326
|
-
try {
|
|
327
|
-
await app.prisma.$queryRaw`SELECT 1`;
|
|
328
|
-
return {
|
|
329
|
-
status: 'ok',
|
|
330
|
-
timestamp: new Date().toISOString(),
|
|
331
|
-
database: 'connected',
|
|
332
|
-
};
|
|
333
|
-
} catch (error) {
|
|
334
|
-
request.log.error(error, 'Database health check failed');
|
|
335
|
-
return reply.status(503).send({
|
|
336
|
-
status: 'error',
|
|
337
|
-
timestamp: new Date().toISOString(),
|
|
338
|
-
database: 'disconnected',
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const { default: usersRoutes } = await import('@/routes/users.js');
|
|
344
|
-
const { default: sessionsRoutes } = await import('@/routes/sessions.js');
|
|
345
|
-
const { default: passwordRoutes } = await import('@/routes/password.js');
|
|
346
|
-
const { default: verificationRoutes } = await import(
|
|
347
|
-
'@/routes/verification.js'
|
|
348
|
-
);
|
|
349
|
-
const { default: uploadsRoutes } = await import('@/routes/uploads.js');
|
|
350
|
-
const { default: uploadsServeRoutes } = await import(
|
|
351
|
-
'@/routes/uploads-serve.js'
|
|
352
|
-
);
|
|
353
|
-
const { default: accountsRoutes } = await import('@/routes/accounts.js');
|
|
354
|
-
const { default: statsRoutes } = await import('@/routes/stats.js');
|
|
355
|
-
const { default: metricsRoutes } = await import('@/routes/metrics.js');
|
|
356
|
-
const { default: adminSessionsRoutes } = await import(
|
|
357
|
-
'@/routes/admin-sessions.js'
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
metricsService.start();
|
|
361
|
-
|
|
362
|
-
await app.register(uploadsServeRoutes);
|
|
363
|
-
|
|
364
|
-
await app.register(
|
|
365
|
-
async (app) => {
|
|
366
|
-
await app.register(usersRoutes);
|
|
367
|
-
await app.register(sessionsRoutes);
|
|
368
|
-
await app.register(passwordRoutes);
|
|
369
|
-
await app.register(verificationRoutes);
|
|
370
|
-
await app.register(uploadsRoutes);
|
|
371
|
-
await app.register(accountsRoutes);
|
|
372
|
-
await app.register(statsRoutes);
|
|
373
|
-
await app.register(metricsRoutes);
|
|
374
|
-
await app.register(adminSessionsRoutes);
|
|
375
|
-
},
|
|
376
|
-
{ prefix: '/api' }
|
|
377
|
-
);
|