-
Notifications
You must be signed in to change notification settings - Fork 0
feat(server): implement Phase 1.1 backend foundation #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a720654
7a64485
add8b14
d555a07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,3 +45,4 @@ coverage/ | |
|
|
||
| # Docker | ||
| docker-compose.override.yml | ||
| apps/server/src/generated/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import baseConfig from '@devradar/eslint-config'; | ||
|
|
||
| export default [ | ||
| ...baseConfig, | ||
| { | ||
| ignores: ['prisma.config.ts', 'prisma/generated/**'], | ||
| }, | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| { | ||
| "name": "@devradar/server", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "license": "MIT", | ||
| "type": "module", | ||
| "main": "./dist/server.js", | ||
|
Comment on lines
+1
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Add Node.js version constraint in engines field. The package uses modern features (ESM, top-level await implied by Prisma/Fastify setup, TypeScript 5.9) but lacks an explicit Node.js version requirement. Add an "engines": {
"node": ">=20.0.0"
}This prevents runtime issues when the codebase uses features unavailable in older Node.js versions. 🤖 Prompt for AI Agents |
||
| "scripts": { | ||
| "dev": "tsx watch --env-file=.env src/server.ts", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Redundant environment loading with tsx --env-file and dotenv dependency. The dev script uses Consider removing the 🤖 Prompt for AI Agents |
||
| "build": "tsc", | ||
| "start": "node dist/server.js", | ||
| "check-types": "tsc --noEmit", | ||
| "lint": "eslint .", | ||
| "lint:fix": "eslint . --fix", | ||
| "clean": "rimraf dist", | ||
| "db:generate": "prisma generate", | ||
| "db:push": "prisma db push", | ||
| "db:migrate": "prisma migrate dev", | ||
| "db:studio": "prisma studio" | ||
|
Comment on lines
+8
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Test script is a placeholder. Line 35 in the test script outputs Would you like me to open an issue to track setting up a test framework (e.g., Vitest, Jest, or Node's built-in test runner) and writing tests for critical paths (auth flows, WebSocket handlers, database operations)? 🤖 Prompt for AI Agents |
||
| }, | ||
| "dependencies": { | ||
| "@devradar/shared": "workspace:*", | ||
| "@fastify/cookie": "^11.0.2", | ||
| "@fastify/cors": "^11.2.0", | ||
| "@fastify/helmet": "^13.0.2", | ||
| "@fastify/jwt": "^10.0.0", | ||
| "@fastify/rate-limit": "^10.3.0", | ||
| "@fastify/websocket": "^11.2.0", | ||
| "@prisma/adapter-pg": "^7.2.0", | ||
| "@prisma/client": "^7.2.0", | ||
|
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cd apps/server && head -50 package.json | cat -nRepository: senutpal/devRadar Length of output: 1778 🌐 Web query:
💡 Result: Yes — Prisma ORM 7.2.0 is the latest stable release as of January 2, 2026. It was published December 17, 2025. [1][2] Sources:
🌐 Web query:
💡 Result: Short summary of known issues affecting @prisma/adapter-pg (v7.x, including 7.2.0):
If you want, I can:
Sources: Citations:
Versions are synchronized correctly, but be aware of known issues with @prisma/adapter-pg. All three Prisma packages are correctly pinned to version 7.2.0, which is the latest stable release (published December 17, 2025). The synchronization across However, there are several known issues with
Keep these in mind during development and testing, and watch for updates addressing these edge cases. |
||
| "dotenv": "^16.5.0", | ||
| "fastify": "^5.6.2", | ||
| "ioredis": "^5.8.2", | ||
| "pg": "^8.16.3", | ||
| "pino": "^10.1.0", | ||
| "pino-pretty": "^13.1.3", | ||
| "zod": "^3.24.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@devradar/eslint-config": "workspace:*", | ||
| "@devradar/tsconfig": "workspace:*", | ||
| "@types/node": "^25.0.3", | ||
| "@types/pg": "^8.16.0", | ||
| "@types/ws": "^8.18.1", | ||
| "eslint": "^9.39.2", | ||
| "prisma": "^7.2.0", | ||
| "rimraf": "^6.1.2", | ||
| "tsx": "^4.21.0", | ||
| "typescript": "^5.9.3" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /** | ||
| * Prisma Configuration | ||
| * | ||
| * Prisma 7 configuration file for CLI commands (migrate, db push, etc.) | ||
| * Database URL for migrations is configured here. | ||
| * | ||
| * IMPORTANT: In Prisma 7, env vars must be explicitly loaded with dotenv. | ||
| */ | ||
|
|
||
| import 'dotenv/config'; | ||
| import { defineConfig, env } from 'prisma/config'; | ||
|
|
||
| export default defineConfig({ | ||
| // Path to the Prisma schema file | ||
| schema: 'prisma/schema.prisma', | ||
|
|
||
| // Migration settings | ||
| migrations: { | ||
| path: 'prisma/migrations', | ||
| }, | ||
|
|
||
| // Database connection for Prisma CLI (migrate, db push, studio) | ||
| datasource: { | ||
| url: env('DATABASE_URL'), | ||
| shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), | ||
| }, | ||
| }); | ||
|
Comment on lines
+10
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Configuration looks good, but consider aligning environment validation. The Prisma 7 configuration is correctly set up with the adapter pattern. However,
🔎 Suggested addition to config.ts // Database
DATABASE_URL: z
.string()
.url()
.refine((url: string) => url.startsWith('postgresql://') || url.startsWith('postgres://'), {
message: 'DATABASE_URL must be a valid PostgreSQL connection string',
}),
+
+ // Shadow database (optional, used for Prisma migrations)
+ SHADOW_DATABASE_URL: z
+ .string()
+ .url()
+ .refine((url: string) => url.startsWith('postgresql://') || url.startsWith('postgres://'), {
+ message: 'SHADOW_DATABASE_URL must be a valid PostgreSQL connection string',
+ })
+ .optional(),
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,110 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // DevRadar Database Schema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Using Prisma 7 with PostgreSQL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| generator client { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider = "prisma-client" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output = "../src/generated/prisma" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| moduleFormat = "esm" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datasource db { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider = "postgresql" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note: URL configured in prisma.config.ts for Prisma 7 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
10
to
13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add datasource URL configuration. The datasource is missing the 🔎 Proposed fix datasource db {
provider = "postgresql"
+ url = env("DATABASE_URL")
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // User Management | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model User { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id String @id @default(cuid()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| githubId String @unique | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| displayName String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| avatarUrl String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tier Tier @default(FREE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privacyMode Boolean @default(false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt DateTime @default(now()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedAt DateTime @updatedAt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Relations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| following Follow[] @relation("Following") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| followers Follow[] @relation("Followers") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teams TeamMember[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ownedTeams Team[] @relation("TeamOwner") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([username]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([githubId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Social Graph | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model Follow { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| followerId String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| followingId String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt DateTime @default(now()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Relations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| follower User @relation("Following", fields: [followerId], references: [id], onDelete: Cascade) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| following User @relation("Followers", fields: [followingId], references: [id], onDelete: Cascade) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@id([followerId, followingId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([followerId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([followingId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
45
to
57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider using a composite primary key instead of a separate ID. The 🔎 Proposed refactor model Follow {
- id String @id @default(cuid())
followerId String
followingId String
createdAt DateTime @default(now())
// Relations
follower User @relation("Following", fields: [followerId], references: [id], onDelete: Cascade)
following User @relation("Followers", fields: [followingId], references: [id], onDelete: Cascade)
- @@unique([followerId, followingId])
+ @@id([followerId, followingId])
@@index([followerId])
@@index([followingId])
}Note: This would require updating 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Team Management | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model Team { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id String @id @default(cuid()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slug String @unique | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ownerId String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tier Tier @default(TEAM) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt DateTime @default(now()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedAt DateTime @updatedAt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Relations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owner User @relation("TeamOwner", fields: [ownerId], references: [id], onDelete: Cascade) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| members TeamMember[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([slug]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([ownerId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
63
to
78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider adding a relation from Team to User for the owner. The
🔎 Proposed fix model Team {
id String @id @default(cuid())
name String
slug String @unique
ownerId String
tier Tier @default(TEAM)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
+ owner User @relation("OwnedTeams", fields: [ownerId], references: [id], onDelete: Cascade)
members TeamMember[]
@@index([slug])
@@index([ownerId])
}Also add the inverse relation on User: model User {
// ... existing fields ...
// Relations
following Follow[] @relation("Following")
followers Follow[] @relation("Followers")
teams TeamMember[]
+ ownedTeams Team[] @relation("OwnedTeams")
// ...
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model TeamMember { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id String @id @default(cuid()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teamId String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| role Role @default(MEMBER) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| joinedAt DateTime @default(now()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Relations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user User @relation(fields: [userId], references: [id], onDelete: Cascade) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@unique([userId, teamId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([userId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@index([teamId]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+80
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider using composite primary key for TeamMember. Similar to the 🔎 Suggested refactor model TeamMember {
- id String @id @default(cuid())
userId String
teamId String
role Role @default(MEMBER)
joinedAt DateTime @default(now())
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
- @@unique([userId, teamId])
+ @@id([userId, teamId])
@@index([userId])
@@index([teamId])
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Enums | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // =================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enum Tier { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FREE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PRO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TEAM | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enum Role { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OWNER | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADMIN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MEMBER | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| /** | ||
| * Environment Configuration | ||
| * | ||
| * Validates and exports environment variables using Zod. | ||
| * Follows 12-factor app principles - all config from environment. | ||
| */ | ||
|
|
||
| import { z } from 'zod'; | ||
|
|
||
| /** | ||
| * Environment schema with validation rules. | ||
| * All required variables must be set or have sensible defaults. | ||
| */ | ||
| const envSchema = z.object({ | ||
| // Node environment | ||
| NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), | ||
|
|
||
| // Server | ||
| PORT: z.coerce.number().int().min(1).max(65535).default(3000), | ||
| HOST: z.string().default('0.0.0.0'), | ||
|
|
||
| // Database | ||
| DATABASE_URL: z | ||
| .string() | ||
| .url() | ||
| .refine((url: string) => url.startsWith('postgresql://') || url.startsWith('postgres://'), { | ||
| message: 'DATABASE_URL must be a valid PostgreSQL connection string', | ||
| }), | ||
|
|
||
| // Redis | ||
| REDIS_URL: z | ||
| .string() | ||
| .url() | ||
| .refine((url: string) => url.startsWith('redis://') || url.startsWith('rediss://'), { | ||
| message: 'REDIS_URL must be a valid Redis connection string', | ||
| }), | ||
|
|
||
| // JWT | ||
| JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'), | ||
| JWT_EXPIRES_IN: z.string().default('7d'), | ||
|
|
||
| // GitHub OAuth | ||
| GITHUB_CLIENT_ID: z.string().min(1, 'GITHUB_CLIENT_ID is required'), | ||
| GITHUB_CLIENT_SECRET: z.string().min(1, 'GITHUB_CLIENT_SECRET is required'), | ||
| GITHUB_CALLBACK_URL: z.string().url(), | ||
|
|
||
| // Logging | ||
| LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'), | ||
| }); | ||
|
|
||
| /** | ||
| * Parsed and validated environment configuration. | ||
| */ | ||
| export type Env = z.infer<typeof envSchema>; | ||
|
|
||
| /** | ||
| * Parse and validate environment variables. | ||
| * Throws descriptive error if validation fails. | ||
| */ | ||
| function parseEnv(): Env { | ||
| const result = envSchema.safeParse(process.env); | ||
|
|
||
| if (!result.success) { | ||
| console.error('❌ Invalid environment variables:\n', result.error.format()); | ||
| throw new Error('Invalid environment configuration'); | ||
| } | ||
|
|
||
| return result.data; | ||
| } | ||
|
Comment on lines
+60
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider more detailed error output for debugging. When validation fails, the current output shows formatted errors but doesn't indicate which variables are missing vs. invalid. In CI/CD environments, this can slow down debugging. 🔎 Enhanced error output function parseEnv(): Env {
const result = envSchema.safeParse(process.env);
if (!result.success) {
- console.error('❌ Invalid environment variables:\n', result.error.format());
+ console.error('❌ Invalid environment variables:');
+ for (const issue of result.error.issues) {
+ console.error(` - ${issue.path.join('.')}: ${issue.message}`);
+ }
throw new Error('Invalid environment configuration');
}
return result.data;
}🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
| * Validated environment configuration. | ||
| * Access this throughout the application for type-safe config. | ||
| */ | ||
| export const env = parseEnv(); | ||
|
|
||
| /** | ||
| * Check if running in production mode. | ||
| */ | ||
| export const isProduction = env.NODE_ENV === 'production'; | ||
|
|
||
| /** | ||
| * Check if running in development mode. | ||
| */ | ||
| export const isDevelopment = env.NODE_ENV === 'development'; | ||
|
|
||
| /** | ||
| * Check if running in test mode. | ||
| */ | ||
| export const isTest = env.NODE_ENV === 'test'; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider moving this rule to a more appropriate section.
The
apps/server/src/generated/ignore rule is placed under the "Docker" section, but it's for Prisma-generated code artifacts. Consider creating a dedicated "Generated files" section or placing it near other build/generated output rules (lines 5-10).🤖 Prompt for AI Agents