Skip to content

Refactor authentication guards and add response schemas - #26

Merged
chakrihacker merged 3 commits into
mainfrom
feat/add-middleware
Nov 22, 2025
Merged

chakrihacker merged 3 commits into
mainfrom
feat/add-middleware

Conversation

@chakrihacker

@chakrihacker chakrihacker commented Nov 22, 2025 •

Copy link
Copy Markdown
Contributor

User description

PR Type

Enhancement, Bug fix


Description

  • Refactored authentication guards with improved code formatting and error handling

  • Introduced comprehensive response schemas for success and error responses

  • Moved response type definitions from utility file to dedicated schema module

  • Added environment configuration template and Prisma configuration file

  • Updated dependencies including better-auth, Prisma, and turbo to latest versions


Diagram Walkthrough

flowchart LR
  A["auth-guards.ts<br/>Refactored guards"] --> B["response.ts<br/>New schemas"]
  B --> C["format-response.ts<br/>Updated imports"]
  D[".env.example<br/>Config template"] --> E["prisma.config.ts<br/>New config"]
  F["package.json files<br/>Dependency updates"] --> G["Updated versions<br/>better-auth, Prisma, turbo"]
Loading

File Walkthrough

Relevant files
Enhancement
3 files
auth-guards.ts
Refactored guards with improved formatting and error messages
+48/-48 
response.ts
New comprehensive response schemas with detailed documentation
+335/-0 
format-response.ts
Refactored to import types from response schema module     
+70/-94 
Formatting
1 files
auth.ts
Updated quote style for consistency                                           
+5/-5     
Configuration changes
2 files
prisma.config.ts
New Prisma configuration with environment variable support
+15/-0   
schema.prisma
Removed engineType and url configuration for external config
+0/-2     
Documentation
2 files
.env.example
New environment variables template for configuration         
+20/-0   
README.md
Added inspiration section with reference link                       
+4/-0     
Dependencies
4 files
package.json
Updated better-auth and expo dependencies to latest versions
+2/-2     
package.json
Updated better-auth and expo dependencies to latest versions
+2/-2     
package.json
Updated turbo dependency to version 2.6.1                               
+1/-1     
package.json
Updated Prisma to v7, added dotenv and pg types dependencies
+5/-3     


CodeAnt-AI Description

Deliver consistent auth failure responses and response schemas

What Changed

  • Authentication guards now return RFC7807-style problem details for unauthorized and forbidden requests, keeping the response body, headers, and status text aligned for clients reacting to auth failures.
  • Success and error responses are built using the new shared schemas, so every response carries consistent request metadata (request IDs, schema version, trace/span IDs) and mirrors the envelope code in the problem details.
  • Added a .env.example template and Prisma config that point to the required environment variables and database URL pattern for local and deployed setups.

Impact

✅ Clearer authentication failure details
✅ Consistent metadata on every API response
✅ Ready-to-use environment template

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

- Simplified the `requireAuth` and `requireRole` functions in `auth-guards.ts` for better readability.
- Introduced a new `response.ts` file containing schemas for success and error responses, including `SuccessOptionsSchema` and `ErrorOptionsSchema`.
- Updated `format-response.ts` to utilize the new response schemas, enhancing type safety and consistency in response formatting.
- Created a new `.env.example` file to provide a template for environment variables.
- Updated `.gitignore` to include `.env` files to prevent local environment configurations from being tracked.
- Updated dependencies in `package.json` files across multiple applications to use the latest versions of `@better-auth/expo` and `better-auth`.
- Enhanced error handling in authentication guards to return string messages for unauthorized and forbidden responses.
- Added Prisma configuration file and updated the schema to use environment variables for database connection.
- Updated `bun.lock` to reflect changes in dependencies and their versions.
@codeant-ai

codeant-ai Bot commented Nov 22, 2025

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@sourcery-ai

sourcery-ai Bot commented Nov 22, 2025 •

Copy link
Copy Markdown

Reviewer's Guide

Refactors response formatting and authentication guards to use new typed response schemas, integrates shared options types, and updates auth-related status handling and dependencies across the repo.

Sequence diagram for unauthenticated request through refactored auth guard

sequenceDiagram
  actor "Client" as Client
  participant "API Route" as ApiRoute
  participant "requireAuth plugin" as RequireAuth
  participant "withAuth plugin" as WithAuth
  participant "Auth service" as AuthService
  participant "Response formatter" as Formatter

  "Client"->>"API Route": "HTTP request"
  "API Route"->>"requireAuth plugin": "enter route handler pipeline"
  "requireAuth plugin"->>"withAuth plugin": "use withAuth()"
  "withAuth plugin"->>"Auth service": "auth.api.getSession({ headers })"
  "Auth service"-->>"withAuth plugin": "session or null"
  "withAuth plugin"-->>"requireAuth plugin": "derive { session, user }"

  alt "missing session or user"
    "requireAuth plugin"->>"Response formatter": "formatErrorResponse({ code: message, problem })"
    "Response formatter"-->>"requireAuth plugin": "EnvelopeError (RFC7807 compatible)"
    "requireAuth plugin"->>"requireAuth plugin": "set.status = 'Unauthorized'"
    "requireAuth plugin"->>"Client": "throw new Response(JSON.stringify(envelope), status 401, headers)"
  else "session and user present"
    "requireAuth plugin"-->>"API Route": "continue to route handler"
    "API Route"-->>"Client": "normal success or downstream response"
  end
Loading

Sequence diagram for unauthorized role check with requireRole

sequenceDiagram
  actor "Client" as Client
  participant "API Route" as ApiRoute
  participant "requireRole plugin" as RequireRole
  participant "requireAuth plugin" as RequireAuth
  participant "Response formatter" as Formatter

  "Client"->>"API Route": "HTTP request"
  "API Route"->>"requireRole plugin": "enter route handler pipeline"
  "requireRole plugin"->>"requireAuth plugin": "use requireAuth() (ensures session and user)"
  "requireAuth plugin"-->>"requireRole plugin": "user in context or error response already thrown"

  "requireRole plugin"->>"requireRole plugin": "check user.role against allowed roles"
  alt "user lacks required role"
    "requireRole plugin"->>"Response formatter": "formatErrorResponse({ code: message, problem })"
    "Response formatter"-->>"requireRole plugin": "EnvelopeError"
    "requireRole plugin"->>"requireRole plugin": "set.status = 'Forbidden'"
    "requireRole plugin"->>"Client": "throw new Response(JSON.stringify(envelope), status 403)"
  else "user has required role"
    "requireRole plugin"-->>"API Route": "continue to route handler"
    "API Route"-->>"Client": "normal success or downstream response"
  end
Loading

Class diagram for typed response option and problem schemas

classDiagram
  class BaseOptions {
    +links: LinkMapSchema?
    +ui: UISchema?
    +meta: Partial<MetaSchema>?
    +schemaVersion: string?
    +requestId: string?
    +traceId: string?
    +spanId: string?
    +locale: string?
    +idempotencyKey: string?
  }

  class SuccessOptions {
    +links: LinkMapSchema?
    +ui: UISchema?
    +meta: Partial<MetaSchema>?
    +schemaVersion: string?
    +requestId: string?
    +traceId: string?
    +spanId: string?
    +locale: string?
    +idempotencyKey: string?
    +etag: string?
    +now(): string?
    +generateRequestId(): string?
  }

  class ErrorOptions {
    +links: LinkMapSchema?
    +ui: UISchema?
    +meta: Partial<MetaSchema>?
    +schemaVersion: string?
    +requestId: string?
    +traceId: string?
    +spanId: string?
    +locale: string?
    +idempotencyKey: string?
    +problemInstance: string?
    +now(): string?
    +generateRequestId(): string?
  }

  class ProblemInput {
    +type: string
    +title: string
    +status: number
    +detail: string?
    +instance: string?
    +code: string?
    +errors: ProblemFieldIssueSchema[]?
    +hint: string?
    +docsUrl: string?
    +supportUrl: string?
    +retryAfterSeconds: number?
  }

  class FormatSuccessParams_D {
    +code: string
    +data: D
    +options: SuccessOptions?
  }

  class FormatErrorParams {
    +code: string
    +problem: ProblemInput
    +options: ErrorOptions?
  }

  class EnvelopeSuccess_D {
    +ok: true
    +code: string
    +data: D
    +error: null
    +links: LinkMapSchema?
    +ui: UISchema?
    +meta: MetaSchema
  }

  class EnvelopeError {
    +ok: false
    +code: string
    +data: null
    +error: Problem
    +links: LinkMapSchema?
    +ui: UISchema?
    +meta: MetaSchema
  }

  class Meta {
    +requestId: string
    +schemaVersion: string
    +generatedAt: string
    +traceId: string?
    +spanId: string?
    +locale: string?
    +idempotencyKey: string?
    +etag: string?
  }

  class FormatResponse {
    +formatSuccessResponse_D(params: FormatSuccessParams_D): EnvelopeSuccess_D
    +formatErrorResponse(params: FormatErrorParams): EnvelopeError
    +buildMeta(options: SuccessOptions or ErrorOptions): Meta
  }

  BaseOptions <|-- SuccessOptions : extends
  BaseOptions <|-- ErrorOptions : extends

  FormatSuccessParams_D --> SuccessOptions : uses options
  FormatErrorParams --> ErrorOptions : uses options
  FormatErrorParams --> ProblemInput : contains problem

  FormatResponse --> FormatSuccessParams_D : accepts
  FormatResponse --> FormatErrorParams : accepts
  FormatResponse --> EnvelopeSuccess_D : returns
  FormatResponse --> EnvelopeError : returns
  FormatResponse --> Meta : builds

  EnvelopeSuccess_D --> Meta : has meta
  EnvelopeError --> Meta : has meta
Loading

Flow diagram for building success and error envelopes with shared options

flowchart TD
  Start(["Start formatting response"]) --> ChoosePath{"Success or error?"}

  ChoosePath -->|"Success"| BuildSuccessParams["Receive FormatSuccessParams<D> (code, data, options)"]
  ChoosePath -->|"Error"| BuildErrorParams["Receive FormatErrorParams (code, problem, options)"]

  BuildSuccessParams --> BuildMetaFromSuccess["buildMeta(options: SuccessOptions)"]
  BuildErrorParams --> BuildMetaFromError["buildMeta(options: ErrorOptions)"]

  BuildMetaFromSuccess --> MetaSuccess["Create Meta: requestId, schemaVersion, generatedAt, traceId, spanId, locale, idempotencyKey, optional etag"]
  BuildMetaFromError --> MetaError["Create Meta: requestId, schemaVersion, generatedAt, traceId, spanId, locale, idempotencyKey"]

  MetaError --> MergeProblem["Merge Problem: set problem.code = problem.code || envelope code; set problem.instance = problem.instance || meta.requestId"]

  MetaSuccess --> BuildSuccessEnvelope["Build EnvelopeSuccess<D>: ok = true, code, data, error = null, optional links and ui, meta"]
  MergeProblem --> BuildErrorEnvelope["Build EnvelopeError: ok = false, code, data = null, error = merged Problem, optional links and ui, meta"]

  BuildSuccessEnvelope --> ReturnSuccess(["Return EnvelopeSuccess<D>"])
  BuildErrorEnvelope --> ReturnError(["Return EnvelopeError"])

  ReturnSuccess --> End(["End"])
  ReturnError --> End
Loading

File-Level Changes

Change Details Files
Introduce centralized response option and problem input schemas and wire them into response formatting utilities.
  • Add a new response schema module defining SuccessOptions, ErrorOptions, ProblemInput, and parameter schemas for formatSuccessResponse/formatErrorResponse using Elysia/TypeBox schemas.
  • Refactor format-response utilities to consume the new SuccessOptions, ErrorOptions, and ProblemInput types instead of locally-declared types.
  • Extract shared constants for random ID generation and simplify meta-building logic while preserving existing envelope shape.
apps/api/src/schemas/response.ts
apps/api/src/utils/format-response.ts
Adjust authentication guard plugins to rely on the updated auth plugin and tweak status handling for unauthorized/forbidden responses.
  • Retain the withAuth Elysia plugin but normalize string quoting and maintain session derivation behavior.
  • Update requireAuth guard to use a consolidated session/user check, build problem envelopes via the new response utilities, and set status using string labels instead of numeric codes.
  • Update requireRole guard to reuse requireAuth, compute role membership defensively, and set forbidden statuses similarly while returning problem envelopes.
apps/api/src/plugins/auth.ts
apps/api/src/plugins/auth-guards.ts
Add Prisma config and environment scaffolding for the database package and bump related dependencies.
  • Introduce a Prisma configuration file that loads DATABASE_URL via dotenv and points to the shared prisma schema and migrations directory.
  • Add dotenv and @types/pg dependencies and bump Prisma packages in the db package.
  • Upgrade better-auth and @better-auth/expo versions in API and frontend apps and bump turbo in the root package.
  • Add a starter .env.example file and minor README updates including an inspiration link.
packages/db/prisma.config.ts
packages/db/package.json
apps/api/package.json
apps/frontend/package.json
package.json
.env.example
README.md
packages/db/prisma/schema.prisma
.gitignore
bun.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Nov 22, 2025 •

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • Chores

    • Updated better-auth libraries to v1.4.1 across API and frontend packages
    • Upgraded Prisma database tools from v6 to v7 with PostgreSQL adapter support
    • Updated Turbo to v2.6.1
  • Documentation

    • Added environment configuration template file with sample variable examples

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Environment variables and configuration templates added. Authentication guard logic updated with revised status handling. Response schema definitions centralized and standardized. Database and dependency updates: Prisma upgraded to v7, better-auth packages bumped to v1.4.1, and turbo updated to v2.6.1.

Changes

Cohort / File(s) Summary
Environment & Configuration
.env.example, .gitignore, README.md
Added .env.example with sample environment variables; expanded .gitignore to include .env; added "Inspiration" subsection to README
Prisma Database Configuration
packages/db/prisma.config.ts, packages/db/prisma/schema.prisma
New Prisma configuration file centralizing datasource, schema path, and migrations settings; removed redundant configuration from schema.prisma
Dependency Updates
apps/api/package.json, apps/frontend/package.json, packages/db/package.json, package.json (root)
Multiple version bumps: better-auth and @better-auth/expo to v1.4.1, Prisma packages to v7.0.0, turbo to v2.6.1; added @types/pg and dotenv as dependencies
Authentication Logic
apps/api/src/plugins/auth-guards.ts, apps/api/src/plugins/auth.ts
Updated requireAuth and requireRole checks with revised status propagation; migrated string literal formatting to double quotes in auth.ts
Response Schema & Formatting
apps/api/src/schemas/response.ts, apps/api/src/utils/format-response.ts
New centralized response schema module with RFC7807-compatible problem definitions and success/error envelopes; refactored format-response.ts to import schemas from centralized location

Sequence Diagram

sequenceDiagram
    participant Client
    participant Elysia
    participant requireAuth
    participant requireRole
    participant Response

    Client->>Elysia: Request with auth context
    Elysia->>requireAuth: Check session & user present
    alt Session & User Valid
        requireAuth->>Elysia: Auth check passed
    else Session or User Missing
        requireAuth->>Response: Set status "Unauthorized"
        Response-->>Client: 401 Problem+JSON response
    end
    
    Elysia->>requireRole: Check role permission
    alt Role Authorized
        requireRole->>Elysia: Role check passed
    else Role Unauthorized
        requireRole->>Response: Set status "Forbidden"
        Response-->>Client: 403 Problem+JSON response
    end
    
    Elysia-->>Client: Request proceeds
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Auth logic changes: Updates to requireAuth and requireRole in auth-guards.ts require verification that status handling (string-based indicators) integrates correctly with response formatting
  • Response schema refactoring: New centralized schema module and refactored format-response.ts; verify all imports and type alignments across modules
  • Prisma v6 to v7 migration: Major version bump requires checking compatibility with existing schema and adapter usage
  • Better-auth v1.3.x to v1.4.1 upgrade: Verify breaking changes or API shifts in both frontend and API packages

Possibly related PRs

Suggested labels

Possible security concern, Review effort 3/5

Poem

🐰 A rabbit hops through schemas bright,
Auth guards now check with all their might,
Prisma leaps from six to seven,
Responses formatted just like heaven,
Dependencies dance in harmony's light! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: refactoring authentication guards and introducing response schemas, which align with the primary enhancements in the changeset.
Description check ✅ Passed The pull request description is well-related to the changeset, providing clear context about authentication guards refactoring, response schemas, configuration updates, and dependency version bumps.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-middleware

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review

Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
⚪
Weak default secrets

Description: Example environment file includes realistic-looking secrets and credentials (e.g.,
postgres://postgres:supersecret..., redis://default:supersecret...) which, if copied to
real configs without changes, could lead to weak default credentials and unintended
exposure; ensure strong, non-default secrets and clarify these are placeholders only.
.env.example [5-14]

Referred Code
DATABASE_URL=postgres://postgres:supersecret@localhost:5432/universal-starter?sslmode=require
DATASET_NAME=universal-
EMAIL_FROM=noreply@yourapp.com
EMAIL_FROM_NAME=Your App Name
EMAIL_PROVIDER=resend
NODEMAILER_PASS=your_app_password
NODEMAILER_SERVICE=gmail
NODEMAILER_USER=your_email@gmail.com
PLUNK_API_KEY=your_plunk_api_key_here
REDIS_URL=redis://default:supersecret@localhost:6379
Incorrect HTTP status

Description: In Unauthorized handler, set.status is assigned the string "Unauthorized" rather than the
numeric status code, risking framework misinterpretation and inconsistent HTTP response
codes that clients could exploit (e.g., receiving 200/undefined while error body indicates
401); set a numeric status code consistently.
auth-guards.ts [10-28]

Referred Code
if (!(session && user)) {
  const { code: status, message } = getStatusCode(401);
  set.status = "Unauthorized";
  const envelope = formatErrorResponse({
    code: message,
    problem: {
      status: status as number,
      title: "User is not authenticated",
      type: "urn:problem-type:authentication-error",
    },
  });

  throw new Response(JSON.stringify(envelope), {
    status: status as number,
    headers: {
      "Content-Type": "application/problem+json",
      "www-authenticate": 'Bearer realm="api"',
    },
  });
Incorrect HTTP status

Description: In Forbidden handler, set.status is set to the string "Forbidden" instead of numeric 403,
which may produce incorrect HTTP status behavior and could bypass client-side handling
based on status codes; ensure numeric status assignment.
auth-guards.ts [41-57]

Referred Code
const { code: status, message } = getStatusCode(403);

set.status = "Forbidden";
const envelope = formatErrorResponse({
  code: message,
  problem: {
    status: status as number,
    title: "User is not authorized",
    type: "urn:problem-type:authorization-error",
  },
});
throw new Response(JSON.stringify(envelope), {
  status: status as number,
  headers: {
    "Content-Type": "application/problem+json",
  },
});
Unsafe env handling

Description: Database URL is read using process.env.DATABASE_URL cast as string without validation,
which may cause runtime misconfigurations or accidental inclusion of secrets in
logs/errors; prefer validated env access (e.g., env() helper) and avoid unsafe casts.
prisma.config.ts [8-8]

Referred Code
url: process.env.DATABASE_URL as string,
// shadowDatabaseUrl: env('SHADOW_DATABASE_URL'),
Ticket Compliance
⚪
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
⚪
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

⚪
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: New authentication and authorization guards return structured errors but do not add any
audit logging for failed auth or authz attempts, which are critical security events.

Referred Code
  if (!(session && user)) {
    const { code: status, message } = getStatusCode(401);
    set.status = "Unauthorized";
    const envelope = formatErrorResponse({
      code: message,
      problem: {
        status: status as number,
        title: "User is not authenticated",
        type: "urn:problem-type:authentication-error",
      },
    });

    throw new Response(JSON.stringify(envelope), {
      status: status as number,
      headers: {
        "Content-Type": "application/problem+json",
        "www-authenticate": 'Bearer realm="api"',
      },
    });
  }
});


 ... (clipped 28 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Input validation scope: Guards rely on withAuth() context but add no explicit input validation or sanitation for
headers beyond delegation, which may be acceptable elsewhere but is not visible in this
diff.

Referred Code
.onBeforeHandle({ as: "scoped" }, ({ session, user, set }) => {
  if (!(session && user)) {
    const { code: status, message } = getStatusCode(401);
    set.status = "Unauthorized";
    const envelope = formatErrorResponse({
      code: message,
      problem: {
        status: status as number,
        title: "User is not authenticated",
        type: "urn:problem-type:authentication-error",
      },
    });

    throw new Response(JSON.stringify(envelope), {
      status: status as number,
      headers: {
        "Content-Type": "application/problem+json",
        "www-authenticate": 'Bearer realm="api"',
      },
    });
  }


 ... (clipped 8 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review

qodo-code-review Bot commented Nov 22, 2025 •

Copy link
Copy Markdown

CI Feedback 🧐

(Feedback updated until commit a175f9c)

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: build-workers

Failed stage: Build and push Workers image [❌]

Failure summary:

The Docker buildx step failed due to an invalid image tag format:
- Command tried to tag image as
ghcr.io/fyndx/universal-starter-workers:-cd81909
- Docker image tags cannot start with a hyphen;
this tag violates the reference format
- As a result, buildx reported: invalid reference format and
aborted the build

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

434:  "Direct push": true,
435:  "Docker exporter": true,
436:  "Multi-platform build": true,
437:  "OCI exporter": true
438:  },
439:  "labels": {
440:  "org.mobyproject.buildkit.worker.moby.host-gateway-ip": "172.17.0.1"
441:  }
442:  }
443:  ],
444:  "name": "default",
445:  "driver": "docker"
446:  }
447:  ##[endgroup]
448:  [command]/usr/bin/docker buildx build --file ./apps/workers/Dockerfile --iidfile /home/runner/work/_temp/docker-actions-toolkit-EcTudx/build-iidfile-0c473e84bf.txt --label org.opencontainers.image.created=--label org.opencontainers.image.description= --label org.opencontainers.image.licenses= --label org.opencontainers.image.revision=cd819097c9198a87574aa3d4b6967b3b8199bbdb --label org.opencontainers.image.source=https://github.com/fyndx/universal-starter --label org.opencontainers.image.title=universal-starter --label org.opencontainers.image.url=https://github.com/fyndx/universal-starter --label org.opencontainers.image.version=pr-26 --tag ghcr.io/fyndx/universal-starter-workers:pr-26 --tag ghcr.io/fyndx/universal-starter-workers:-cd81909 --metadata-file /home/runner/work/_temp/docker-actions-toolkit-EcTudx/build-metadata-77bd22096c.json --push .
449:  ERROR: failed to build: invalid tag "ghcr.io/fyndx/universal-starter-workers:-cd81909": invalid reference format
450:  ##[error]buildx failed with: ERROR: failed to build: invalid tag "ghcr.io/fyndx/universal-starter-workers:-cd81909": invalid reference format
451:  Post job cleanup.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • In the auth guards, you're now setting set.status to string values ("Unauthorized"/"Forbidden") while also computing numeric codes via getStatusCode; consider setting set.status directly to the numeric status for consistency and to avoid relying on framework-specific string coercion.
  • In packages/db/prisma.config.ts, since you're bypassing env("DATABASE_URL") and using process.env.DATABASE_URL as string, it would be safer to explicitly validate that the variable is defined and throw a clear error if it's missing to avoid obscure runtime failures.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the auth guards, you're now setting `set.status` to string values ("Unauthorized"/"Forbidden") while also computing numeric codes via `getStatusCode`; consider setting `set.status` directly to the numeric status for consistency and to avoid relying on framework-specific string coercion.
- In `packages/db/prisma.config.ts`, since you're bypassing `env("DATABASE_URL")` and using `process.env.DATABASE_URL as string`, it would be safer to explicitly validate that the variable is defined and throw a clear error if it's missing to avoid obscure runtime failures.

## Individual Comments

### Comment 1
<location> `packages/db/prisma.config.ts:7-8` </location>
<code_context>
+export default defineConfig({
+  datasource: {
+    // Github issue: https://github.com/prisma/prisma/issues/28590
+    // url: env("DATABASE_URL"),
+    url: process.env.DATABASE_URL as string,
+    // shadowDatabaseUrl: env('SHADOW_DATABASE_URL'),
+  },
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Blind cast of `process.env.DATABASE_URL` to string can hide configuration errors.

Casting `process.env.DATABASE_URL` to `string` skips validation and allows `undefined` through, only failing later as a Prisma connection error. Instead, read it into a local variable, throw a clear error if it’s missing or falsy, and then pass that validated value to `url` so you keep the Prisma workaround without hiding config issues.
</issue_to_address>

### Comment 2
<location> `apps/api/src/utils/format-response.ts:18-24` </location>
<code_context>
-	problemInstance?: string;
-};
+const BASE_36_RADIX = 36;
+const RANDOM_ID_LENGTH = 10;
+const RANDOM_ID_START_INDEX = 2;

 const defaultNow = () => new Date().toISOString();
 const defaultId = () =>
-	globalThis.crypto?.randomUUID?.() ??
-	`req_${Math.random().toString(36).slice(2, 10)}`;
+  globalThis.crypto?.randomUUID?.() ??
+  `req_${Math.random().toString(BASE_36_RADIX).slice(RANDOM_ID_START_INDEX, RANDOM_ID_LENGTH)}`;

 function buildMeta(opts: SuccessOptions | ErrorOptions): Meta {
</code_context>

<issue_to_address>
**nitpick:** `RANDOM_ID_LENGTH` name is misleading because it is used as the slice end index, not the length.

In `defaultId`, `slice(RANDOM_ID_START_INDEX, RANDOM_ID_LENGTH)` uses `RANDOM_ID_LENGTH` as an end index, so the substring length is `RANDOM_ID_LENGTH - RANDOM_ID_START_INDEX` (currently 8 chars, not 10). This makes the name misleading and invites off-by-one errors later. Please either rename it to something like `RANDOM_ID_END_INDEX` or derive the end index from a true length constant, e.g. `const RANDOM_ID_LENGTH = 8; slice(RANDOM_ID_START_INDEX, RANDOM_ID_START_INDEX + RANDOM_ID_LENGTH)`.
</issue_to_address>

### Comment 3
<location> `apps/api/src/schemas/response.ts:30` </location>
<code_context>
+/**
+ * BaseOptionsSchema - Common fields shared between SuccessOptions and ErrorOptions
+ */
+const BaseOptionsSchema = t.Object({
+  /**
+   * HATEOAS-style links for navigation (self, next, prev, first, last)
</code_context>

<issue_to_address>
**issue (complexity):** Consider flattening the option schemas, inlining trivial aliases, and separating payload types from internal helpers to make the response types easier to read and reason about at a glance.

You can keep all the runtime-schema features while flattening a few layers to reduce cognitive load.

1) Flatten SuccessOptionsSchema / ErrorOptionsSchema (remove BaseOptionsSchema + Composite)

Instead of `BaseOptionsSchema` + `t.Composite`, define the two option schemas as flat objects. This avoids jumping between multiple definitions just to see the shape.

Current (simplified):

```ts
const BaseOptionsSchema = t.Object({
  links: t.Optional(LinkMapSchema),
  ui: t.Optional(UISchema),
  meta: t.Optional(t.Partial(MetaSchema)),
  schemaVersion: OptionalStringSchema,
  requestId: OptionalStringSchema,
  traceId: OptionalStringSchema,
  spanId: OptionalStringSchema,
  locale: OptionalStringSchema,
  idempotencyKey: OptionalStringSchema,
});

export const SuccessOptionsSchema = t.Composite([
  BaseOptionsSchema,
  t.Object({
    etag: OptionalStringSchema,
  }),
]);

export const ErrorOptionsSchema = t.Composite([
  BaseOptionsSchema,
  t.Object({
    problemInstance: OptionalStringSchema,
  }),
]);
```

Suggested:

```ts
export const SuccessOptionsSchema = t.Object({
  links: t.Optional(LinkMapSchema),
  ui: t.Optional(UISchema),
  meta: t.Optional(t.Partial(MetaSchema)),
  schemaVersion: t.Optional(t.String()),
  requestId: t.Optional(t.String()),
  traceId: t.Optional(t.String()),
  spanId: t.Optional(t.String()),
  locale: t.Optional(t.String()),
  idempotencyKey: t.Optional(t.String()),
  etag: t.Optional(t.String()),
});

export const ErrorOptionsSchema = t.Object({
  links: t.Optional(LinkMapSchema),
  ui: t.Optional(UISchema),
  meta: t.Optional(t.Partial(MetaSchema)),
  schemaVersion: t.Optional(t.String()),
  requestId: t.Optional(t.String()),
  traceId: t.Optional(t.String()),
  spanId: t.Optional(t.String()),
  locale: t.Optional(t.String()),
  idempotencyKey: t.Optional(t.String()),
  problemInstance: t.Optional(t.String()),
});
```

This duplicates a handful of fields but makes the core schemas immediately readable where they’re used.

2) Separate payload schemas from internal helper options (now / generateRequestId)

Right now, the public `SuccessOptions`/`ErrorOptions` types are “schema static type + intersection with helpers”, which makes it harder to see the real shape.

Current:

```ts
export type SuccessOptions = Static<typeof SuccessOptionsSchema> & {
  now?: () => string;
  generateRequestId?: () => string;
};

export type ErrorOptions = Static<typeof ErrorOptionsSchema> & {
  now?: () => string;
  generateRequestId?: () => string;
};
```

Suggested: introduce payload types and keep internal helpers purely in TS:

```ts
export type SuccessOptionsPayload = Static<typeof SuccessOptionsSchema>;
export type ErrorOptionsPayload = Static<typeof ErrorOptionsSchema>;

export type SuccessOptions = SuccessOptionsPayload & {
  now?: () => string;
  generateRequestId?: () => string;
};

export type ErrorOptions = ErrorOptionsPayload & {
  now?: () => string;
  generateRequestId?: () => string;
};
```

Then use the payload schemas for runtime validation, and the richer TS types inside your formatters:

```ts
export const FormatSuccessParamsSchema = <D extends TSchema>(dataSchema: D) =>
  t.Object({
    code: t.String(),
    data: dataSchema,
    options: t.Optional(SuccessOptionsSchema), // payload only
  });

export const FormatErrorParamsSchema = t.Object({
  code: t.String(),
  problem: ProblemInputSchema,
  options: t.Optional(ErrorOptionsSchema), // payload only
});
```

3) Decouple FormatErrorParams type from schema intersection

You currently tweak the schema-derived type via intersection:

```ts
export type FormatErrorParams = Static<typeof FormatErrorParamsSchema> & {
  options?: ErrorOptions;
};
```

That means `options` is “payload in schema” but “payload + helper functions” in the TS type, which is subtle.

After introducing `ErrorOptionsPayload` above, you can define the parameter type explicitly and avoid intersecting with the schema type:

```ts
export type FormatErrorParams = {
  code: string;
  problem: ProblemInput;
  options?: ErrorOptions;
};
```

This keeps:

- Schema-driven runtime validation for the payload (`FormatErrorParamsSchema`),
- A clear, standalone TS type for the function parameters that includes the helper functions on `options`.

4) Inline tiny helper schemas where they don’t add clarity

`OptionalStringSchema` and `CodeStringSchema` don’t carry any extra semantics beyond `t.Optional(t.String())` and `t.String()` and force the reader to chase aliases.

Where they aren’t adding real constraints (unlike a specialized email/uuid pattern), consider inlining:

Current:

```ts
const OptionalStringSchema = t.Optional(t.String());
const CodeStringSchema = t.String();

schemaVersion: OptionalStringSchema,
code: CodeStringSchema,
```

Suggested:

```ts
schemaVersion: t.Optional(t.String()),
code: t.String(),
```

You can keep `UriStringSchema` if you want to emphasize the “URI” constraint, but inlining the purely cosmetic aliases will make the object shapes faster to scan.

5) Lightly trim ProblemInputSchema verbosity

The schema itself is straightforward but hard to scan due to comment density plus helpers. Keeping the semantics but reducing per-field prose makes it more approachable.

For example:

```ts
export const ProblemInputSchema = t.Object({
  // RFC7807: required type URI that identifies the problem type
  type: t.String({ format: "uri" }),

  // RFC7807: short, human-readable summary
  title: t.String(),

  // RFC7807: HTTP status code for this occurrence
  status: t.Integer(),

  detail: t.Optional(t.String()),
  instance: t.Optional(t.String()),
  code: t.Optional(t.String()),
  errors: t.Optional(t.Array(ProblemFieldIssueSchema)),
  hint: t.Optional(t.String()),
  docsUrl: t.Optional(t.String({ format: "uri" })),
  supportUrl: t.Optional(t.String({ format: "uri" })),
  retryAfterSeconds: t.Optional(t.Integer({ minimum: 0 })),
});
```

You still document key RFC7807 semantics but reduce vertical noise and alias indirection, making the shape immediately obvious.

These changes preserve all of your runtime validation and typing but remove several layers of indirection, making the core response shapes easier to understand at a glance.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +7 to +8
// url: env("DATABASE_URL"),
url: process.env.DATABASE_URL as string,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Blind cast of process.env.DATABASE_URL to string can hide configuration errors.

Casting process.env.DATABASE_URL to string skips validation and allows undefined through, only failing later as a Prisma connection error. Instead, read it into a local variable, throw a clear error if it’s missing or falsy, and then pass that validated value to url so you keep the Prisma workaround without hiding config issues.

Comment on lines +18 to +24
const RANDOM_ID_LENGTH = 10;
const RANDOM_ID_START_INDEX = 2;

const defaultNow = () => new Date().toISOString();
const defaultId = () =>
globalThis.crypto?.randomUUID?.() ??
`req_${Math.random().toString(36).slice(2, 10)}`;
globalThis.crypto?.randomUUID?.() ??
`req_${Math.random().toString(BASE_36_RADIX).slice(RANDOM_ID_START_INDEX, RANDOM_ID_LENGTH)}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: RANDOM_ID_LENGTH name is misleading because it is used as the slice end index, not the length.

In defaultId, slice(RANDOM_ID_START_INDEX, RANDOM_ID_LENGTH) uses RANDOM_ID_LENGTH as an end index, so the substring length is RANDOM_ID_LENGTH - RANDOM_ID_START_INDEX (currently 8 chars, not 10). This makes the name misleading and invites off-by-one errors later. Please either rename it to something like RANDOM_ID_END_INDEX or derive the end index from a true length constant, e.g. const RANDOM_ID_LENGTH = 8; slice(RANDOM_ID_START_INDEX, RANDOM_ID_START_INDEX + RANDOM_ID_LENGTH).

/**
* BaseOptionsSchema - Common fields shared between SuccessOptions and ErrorOptions
*/
const BaseOptionsSchema = t.Object({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider flattening the option schemas, inlining trivial aliases, and separating payload types from internal helpers to make the response types easier to read and reason about at a glance.

You can keep all the runtime-schema features while flattening a few layers to reduce cognitive load.

  1. Flatten SuccessOptionsSchema / ErrorOptionsSchema (remove BaseOptionsSchema + Composite)

Instead of BaseOptionsSchema + t.Composite, define the two option schemas as flat objects. This avoids jumping between multiple definitions just to see the shape.

Current (simplified):

const BaseOptionsSchema = t.Object({
  links: t.Optional(LinkMapSchema),
  ui: t.Optional(UISchema),
  meta: t.Optional(t.Partial(MetaSchema)),
  schemaVersion: OptionalStringSchema,
  requestId: OptionalStringSchema,
  traceId: OptionalStringSchema,
  spanId: OptionalStringSchema,
  locale: OptionalStringSchema,
  idempotencyKey: OptionalStringSchema,
});

export const SuccessOptionsSchema = t.Composite([
  BaseOptionsSchema,
  t.Object({
    etag: OptionalStringSchema,
  }),
]);

export const ErrorOptionsSchema = t.Composite([
  BaseOptionsSchema,
  t.Object({
    problemInstance: OptionalStringSchema,
  }),
]);

Suggested:

export const SuccessOptionsSchema = t.Object({
  links: t.Optional(LinkMapSchema),
  ui: t.Optional(UISchema),
  meta: t.Optional(t.Partial(MetaSchema)),
  schemaVersion: t.Optional(t.String()),
  requestId: t.Optional(t.String()),
  traceId: t.Optional(t.String()),
  spanId: t.Optional(t.String()),
  locale: t.Optional(t.String()),
  idempotencyKey: t.Optional(t.String()),
  etag: t.Optional(t.String()),
});

export const ErrorOptionsSchema = t.Object({
  links: t.Optional(LinkMapSchema),
  ui: t.Optional(UISchema),
  meta: t.Optional(t.Partial(MetaSchema)),
  schemaVersion: t.Optional(t.String()),
  requestId: t.Optional(t.String()),
  traceId: t.Optional(t.String()),
  spanId: t.Optional(t.String()),
  locale: t.Optional(t.String()),
  idempotencyKey: t.Optional(t.String()),
  problemInstance: t.Optional(t.String()),
});

This duplicates a handful of fields but makes the core schemas immediately readable where they’re used.

  1. Separate payload schemas from internal helper options (now / generateRequestId)

Right now, the public SuccessOptions/ErrorOptions types are “schema static type + intersection with helpers”, which makes it harder to see the real shape.

Current:

export type SuccessOptions = Static<typeof SuccessOptionsSchema> & {
  now?: () => string;
  generateRequestId?: () => string;
};

export type ErrorOptions = Static<typeof ErrorOptionsSchema> & {
  now?: () => string;
  generateRequestId?: () => string;
};

Suggested: introduce payload types and keep internal helpers purely in TS:

export type SuccessOptionsPayload = Static<typeof SuccessOptionsSchema>;
export type ErrorOptionsPayload = Static<typeof ErrorOptionsSchema>;

export type SuccessOptions = SuccessOptionsPayload & {
  now?: () => string;
  generateRequestId?: () => string;
};

export type ErrorOptions = ErrorOptionsPayload & {
  now?: () => string;
  generateRequestId?: () => string;
};

Then use the payload schemas for runtime validation, and the richer TS types inside your formatters:

export const FormatSuccessParamsSchema = <D extends TSchema>(dataSchema: D) =>
  t.Object({
    code: t.String(),
    data: dataSchema,
    options: t.Optional(SuccessOptionsSchema), // payload only
  });

export const FormatErrorParamsSchema = t.Object({
  code: t.String(),
  problem: ProblemInputSchema,
  options: t.Optional(ErrorOptionsSchema), // payload only
});
  1. Decouple FormatErrorParams type from schema intersection

You currently tweak the schema-derived type via intersection:

export type FormatErrorParams = Static<typeof FormatErrorParamsSchema> & {
  options?: ErrorOptions;
};

That means options is “payload in schema” but “payload + helper functions” in the TS type, which is subtle.

After introducing ErrorOptionsPayload above, you can define the parameter type explicitly and avoid intersecting with the schema type:

export type FormatErrorParams = {
  code: string;
  problem: ProblemInput;
  options?: ErrorOptions;
};

This keeps:

  • Schema-driven runtime validation for the payload (FormatErrorParamsSchema),
  • A clear, standalone TS type for the function parameters that includes the helper functions on options.
  1. Inline tiny helper schemas where they don’t add clarity

OptionalStringSchema and CodeStringSchema don’t carry any extra semantics beyond t.Optional(t.String()) and t.String() and force the reader to chase aliases.

Where they aren’t adding real constraints (unlike a specialized email/uuid pattern), consider inlining:

Current:

const OptionalStringSchema = t.Optional(t.String());
const CodeStringSchema = t.String();

schemaVersion: OptionalStringSchema,
code: CodeStringSchema,

Suggested:

schemaVersion: t.Optional(t.String()),
code: t.String(),

You can keep UriStringSchema if you want to emphasize the “URI” constraint, but inlining the purely cosmetic aliases will make the object shapes faster to scan.

  1. Lightly trim ProblemInputSchema verbosity

The schema itself is straightforward but hard to scan due to comment density plus helpers. Keeping the semantics but reducing per-field prose makes it more approachable.

For example:

export const ProblemInputSchema = t.Object({
  // RFC7807: required type URI that identifies the problem type
  type: t.String({ format: "uri" }),

  // RFC7807: short, human-readable summary
  title: t.String(),

  // RFC7807: HTTP status code for this occurrence
  status: t.Integer(),

  detail: t.Optional(t.String()),
  instance: t.Optional(t.String()),
  code: t.Optional(t.String()),
  errors: t.Optional(t.Array(ProblemFieldIssueSchema)),
  hint: t.Optional(t.String()),
  docsUrl: t.Optional(t.String({ format: "uri" })),
  supportUrl: t.Optional(t.String({ format: "uri" })),
  retryAfterSeconds: t.Optional(t.Integer({ minimum: 0 })),
});

You still document key RFC7807 semantics but reduce vertical noise and alias indirection, making the shape immediately obvious.

These changes preserve all of your runtime validation and typing but remove several layers of indirection, making the core response shapes easier to understand at a glance.

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Nov 22, 2025
@qodo-code-review

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
The new response schema is over-engineered

The new response schema in apps/api/src/schemas/response.ts is overly complex
with features like HATEOAS and UI hints. It's suggested to simplify this schema
to reduce cognitive overhead and better fit the project's current needs.

Examples:

apps/api/src/schemas/response.ts [27-90]
/**
 * BaseOptionsSchema - Common fields shared between SuccessOptions and ErrorOptions
 */
const BaseOptionsSchema = t.Object({
  /**
   * HATEOAS-style links for navigation (self, next, prev, first, last)
   * Useful for pagination and resource discovery
   */
  links: t.Optional(LinkMapSchema),


 ... (clipped 54 lines)

Solution Walkthrough:

Before:

// apps/api/src/schemas/response.ts
const BaseOptionsSchema = t.Object({
  // HATEOAS-style links for navigation
  links: t.Optional(LinkMapSchema),
  // UI metadata for frontend rendering
  ui: t.Optional(UISchema),
  // Extensive metadata
  meta: t.Optional(t.Partial(MetaSchema)),
  schemaVersion: OptionalStringSchema,
  requestId: OptionalStringSchema,
  traceId: OptionalStringSchema, // OpenTelemetry
  spanId: OptionalStringSchema,  // OpenTelemetry
  locale: OptionalStringSchema,
  idempotencyKey: OptionalStringSchema,
});

export const SuccessOptionsSchema = t.Composite([
  BaseOptionsSchema,
  // ...
]);

After:

// A simpler schema could be defined in format-response.ts or a schema file.
// This example shows a much simpler structure.

type SuccessOptions = {
  requestId?: string;
  meta?: Record<string, any>;
};

function formatSuccessResponse<D>({ code, data, options = {} }) {
  const envelope = {
    ok: true,
    code,
    data,
    meta: {
      requestId: options.requestId ?? generateId(),
      generatedAt: new Date().toISOString(),
      ...options.meta,
    },
  };
  return envelope;
}
Suggestion importance[1-10]: 7

__

Why: The suggestion raises a valid and significant concern about potential over-engineering in the new API response schema, which is a core architectural component with long-term implications for maintainability and developer experience.

Medium
Possible issue
Validate environment variable before use

Validate that the DATABASE_URL environment variable is set before using it, and
throw an error if it is missing to prevent potential runtime failures.

packages/db/prisma.config.ts [6-8]

 // Github issue: https://github.com/prisma/prisma/issues/28590
 // url: env("DATABASE_URL"),
-url: process.env.DATABASE_URL as string,
+url: (() => {
+  if (!process.env.DATABASE_URL) {
+    throw new Error("DATABASE_URL environment variable is not set.");
+  }
+  return process.env.DATABASE_URL;
+})(),
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential runtime error from an unset environment variable and proposes a robust check, which is a good practice for configuration management.

Low
  • More

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
README.md (1)

97-99: Consider formatting the URL as a proper markdown link.

The bare URL on line 99 should be formatted as a markdown link for better readability and to satisfy markdown linting rules.

Apply this diff to format the URL properly:

 # Inspiration
 
-- https://www.better-t-stack.dev/
+- [better-t-stack](https://www.better-t-stack.dev/)
.env.example (1)

20-20: Add a trailing newline at the end of the file.

Environment files should end with a blank line as per POSIX standards and to satisfy linting rules.

Add a blank line after line 20.

packages/db/prisma.config.ts (1)

5-10: Add a clear guard for missing DATABASE_URL

Casting process.env.DATABASE_URL to string hides the possibility of it being undefined, which can lead to confusing runtime errors when Prisma tries to connect.

Consider guarding the value once up front and failing fast with a clear message:

-import "dotenv/config";
-import { defineConfig, env } from "prisma/config";
-
-export default defineConfig({
-  datasource: {
-    // Github issue: https://github.com/prisma/prisma/issues/28590
-    // url: env("DATABASE_URL"),
-    url: process.env.DATABASE_URL as string,
-    // shadowDatabaseUrl: env('SHADOW_DATABASE_URL'),
-  },
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+const databaseUrl = process.env.DATABASE_URL;
+if (!databaseUrl) {
+  throw new Error("DATABASE_URL is not set");
+}
+
+export default defineConfig({
+  datasource: {
+    // Github issue: https://github.com/prisma/prisma/issues/28590
+    // url: env("DATABASE_URL"),
+    url: databaseUrl,
+    // shadowDatabaseUrl: env("SHADOW_DATABASE_URL"),
+  },
   schema: "./prisma/schema.prisma",
   migrations: {
     path: "./prisma/migrations",
   },
 });
apps/api/src/plugins/auth-guards.ts (1)

32-59: Avoid double‑applying withAuth() in requireRole

requireRole currently does:

  • .use(withAuth())
  • .use(requireAuth()), and requireAuth() itself already calls .use(withAuth()).

This means the auth plugin (and its derive logic) runs twice for guarded routes, which is redundant.

You can simplify the chain to rely on requireAuth() for both auth wiring and 401 handling:

-export const requireRole = (roles: string[]) =>
-  new Elysia({ name: "requireRole" })
-    .use(withAuth())
-    .use(requireAuth())
+export const requireRole = (roles: string[]) =>
+  new Elysia({ name: "requireRole" })
+    .use(requireAuth())
     .onBeforeHandle({ as: "scoped" }, ({ user, set }) => {
       const { role } = user || {};
       const hasRole = role && roles.includes(role);

       if (!hasRole) {
         const { code: status, message } = getStatusCode(403);

         set.status = "Forbidden";
         const envelope = formatErrorResponse({
           code: message,
           problem: {
             status: status as number,
             title: "User is not authorized",
             type: "urn:problem-type:authorization-error",
           },
         });
         throw new Response(JSON.stringify(envelope), {
           status: status as number,
           headers: {
             "Content-Type": "application/problem+json",
           },
         });
       }
     });
apps/api/src/utils/format-response.ts (1)

17-25: Optional: use stronger randomness for request ID fallback

The Math.random() base‑36 fallback is fine for diagnostic IDs, but it’s not ideal if these IDs are ever relied on for anything security‑sensitive (e.g., correlating logs across trust boundaries).

If you want a stronger fallback while still avoiding randomUUID, you could use crypto.getRandomValues where available:

-const defaultId = () =>
-  globalThis.crypto?.randomUUID?.() ??
-  `req_${Math.random().toString(BASE_36_RADIX).slice(RANDOM_ID_START_INDEX, RANDOM_ID_LENGTH)}`;
+const defaultId = () => {
+  if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
+
+  if (globalThis.crypto?.getRandomValues) {
+    const bytes = new Uint8Array(RANDOM_ID_LENGTH);
+    globalThis.crypto.getRandomValues(bytes);
+    const encoded = Array.from(bytes, (b) =>
+      b.toString(BASE_36_RADIX).padStart(2, "0"),
+    ).join("");
+    return `req_${encoded.slice(0, RANDOM_ID_LENGTH)}`;
+  }
+
+  return `req_${Math.random()
+    .toString(BASE_36_RADIX)
+    .slice(RANDOM_ID_START_INDEX, RANDOM_ID_LENGTH)}`;
+}

(Only worth it if you expect these IDs to carry more than diagnostic value.)

apps/api/src/schemas/response.ts (1)

134-144: Clarify how problemInstance in ErrorOptions is intended to be used

ErrorOptionsSchema adds a problemInstance field, and ErrorOptions exposes it at the type level, but the formatter (formatErrorResponse) currently only derives problem.instance from problem.instance ?? meta.requestId and never consults options.problemInstance.

This can be confusing for callers who might expect setting problemInstance on options to influence the resulting problem.

Consider one of:

  • Wire it through: have formatErrorResponse prefer options.problemInstance when problem.instance is absent, e.g.:

    export function formatErrorResponse({
      code,
      problem,
      options = {},
    }: FormatErrorParams): EnvelopeError {
      const meta = buildMeta(options);
  • const mergedProblem: Problem = {

  • ...problem,
    
  • code: problem.code ?? code,
    
  • instance: problem.instance ?? meta.requestId,
    
  • } as Problem;

  • const mergedProblem: Problem = {
  • ...problem,
    
  • code: problem.code ?? code,
    
  • instance:
    
  •   problem.instance ??
    
  •   (options as ErrorOptions).problemInstance ??
    
  •   meta.requestId,
    
  • } as Problem;
    }

- **Or remove `problemInstance`** from `ErrorOptionsSchema` and `ErrorOptions` if you don’t plan to support that override path, to keep the surface area minimal and unambiguous.

Either direction is fine; the main thing is to align the schema/type with the actual formatter behavior.


Also applies to: 301-326

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c508a17c63bd958096313b2b2d3d8c9163aa4fcd and a175f9ce5a98740d5923589a92f3b8847df6d3c3.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `bun.lock` is excluded by `!**/*.lock`

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `.env.example` (1 hunks)
* `.gitignore` (1 hunks)
* `README.md` (1 hunks)
* `apps/api/package.json` (2 hunks)
* `apps/api/src/plugins/auth-guards.ts` (1 hunks)
* `apps/api/src/plugins/auth.ts` (1 hunks)
* `apps/api/src/schemas/response.ts` (1 hunks)
* `apps/api/src/utils/format-response.ts` (1 hunks)
* `apps/frontend/package.json` (2 hunks)
* `package.json` (1 hunks)
* `packages/db/package.json` (1 hunks)
* `packages/db/prisma.config.ts` (1 hunks)
* `packages/db/prisma/schema.prisma` (0 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* packages/db/prisma/schema.prisma

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (3)</summary>

<details>
<summary>**/*.{ts,tsx}</summary>


**📄 CodeRabbit inference engine (.github/copilot-instructions.md)**

> `**/*.{ts,tsx}`: Use object destructuring for method parameters (wrap multiple parameters in an object; define shapes with TypeScript interfaces)
> Use authClient.useSession() for session state
> Avoid React hooks; use Legend State observables directly (exception: Better Auth hooks)
> Import UI components via path aliases (e.g., @/src/components/ui/)
> Prefer union string/number literals over TypeScript enums
> Use export type and import type for types
> Don't use non-null assertions (!) or implicit any
> Avoid TypeScript namespaces; prefer ESM modules

Files:
- `apps/api/src/plugins/auth-guards.ts`
- `packages/db/prisma.config.ts`
- `apps/api/src/plugins/auth.ts`
- `apps/api/src/utils/format-response.ts`
- `apps/api/src/schemas/response.ts`

</details>
<details>
<summary>**/*.{tsx,ts}</summary>


**📄 CodeRabbit inference engine (.github/copilot-instructions.md)**

> Use Platform.OS and process.env.EXPO_OS for conditional platform logic

Files:
- `apps/api/src/plugins/auth-guards.ts`
- `packages/db/prisma.config.ts`
- `apps/api/src/plugins/auth.ts`
- `apps/api/src/utils/format-response.ts`
- `apps/api/src/schemas/response.ts`

</details>
<details>
<summary>**/*.{ts,tsx,js,jsx}</summary>


**📄 CodeRabbit inference engine (.github/copilot-instructions.md)**

> `**/*.{ts,tsx,js,jsx}`: Don't use the arguments object
> Use for...of instead of Array.forEach
> Use arrow functions instead of function expressions
> Use Date.now() instead of new Date().getTime()
> Use .flatMap() instead of map().flat() when possible
> Prefer regex literals over RegExp constructor when possible
> Use concise optional chaining instead of chained logical expressions
> Always provide radix to parseInt()
> Don't use var; use const/let and prefer const when single assignment
> Use template literals over string concatenation
> Use strict equality (===/!==)
> Don't use console or debugger in committed code
> Prevent import cycles and avoid namespace imports
> Don't hardcode secrets (API keys/tokens) in source
> Don't have unused vars, params, imports, labels, or private members
> Avoid await inside loops; ensure Promise-like statements are handled
> Disallow bitwise operators and comma operator
> Ensure switch statements are exhaustive; default clause comes last; no fallthrough or duplicate cases
> Use object spread over Object.assign when creating new objects
> Prefer Array.isArray over instanceof Array; use at() instead of integer index access where appropriate
> No unreachable code, duplicate conditions, or assignments in conditionals
> Always use radix with parseInt; prefer Number.isNaN/Number.isFinite over globals
> Disallow using global eval, with, var, and unsafe negation
> Ensure typeof comparisons use valid strings; getters return values; constructors don’t return values

Files:
- `apps/api/src/plugins/auth-guards.ts`
- `packages/db/prisma.config.ts`
- `apps/api/src/plugins/auth.ts`
- `apps/api/src/utils/format-response.ts`
- `apps/api/src/schemas/response.ts`

</details>

</details><details>
<summary>🧠 Learnings (6)</summary>

<details>
<summary>📚 Learning: 2025-09-03T13:07:10.649Z</summary>

Learnt from: CR
Repo: fyndx/universal-starter PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-03T13:07:10.649Z
Learning: Applies to **/*.{tsx,ts} : Use Platform.OS and process.env.EXPO_OS for conditional platform logic


**Applied to files:**
- `.gitignore`

</details>
<details>
<summary>📚 Learning: 2025-08-28T12:01:24.375Z</summary>

Learnt from: chakrihacker
Repo: fyndx/universal-starter PR: 13
File: apps/frontend/tsconfig.json:13-17
Timestamp: 2025-08-28T12:01:24.375Z
Learning: When using TypeScript project references for Eden Treaty type safety, the backend needs both "composite": true and "declaration": true in tsconfig.json to emit .d.ts files that the frontend can consume. The workspace root also needs TypeScript as a devDependency for the build commands to work properly.


**Applied to files:**
- `package.json`
- `packages/db/package.json`

</details>
<details>
<summary>📚 Learning: 2025-09-03T13:07:10.649Z</summary>

Learnt from: CR
Repo: fyndx/universal-starter PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-03T13:07:10.649Z
Learning: Applies to **/*.{ts,tsx} : Use authClient.useSession() for session state


**Applied to files:**
- `apps/api/src/plugins/auth-guards.ts`
- `apps/api/src/plugins/auth.ts`

</details>
<details>
<summary>📚 Learning: 2025-09-03T13:07:10.649Z</summary>

Learnt from: CR
Repo: fyndx/universal-starter PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-03T13:07:10.649Z
Learning: Applies to src/lib/auth-client.ts : Initialize Better Auth client in src/lib/auth-client.ts with platform-specific plugins


**Applied to files:**
- `apps/api/src/plugins/auth-guards.ts`
- `apps/api/src/plugins/auth.ts`

</details>
<details>
<summary>📚 Learning: 2025-09-02T09:56:34.362Z</summary>

Learnt from: chakrihacker
Repo: fyndx/universal-starter PR: 15
File: apps/backend/package.json:43-55
Timestamp: 2025-09-02T09:56:34.362Z
Learning: For Eden Treaty type safety in monorepo setups, TypeScript source files can be referenced directly in package.json "types" fields (e.g., "./src/index.ts") since Eden Treaty works with compile-time type inference and doesn't require emitted .d.ts files for local development.


**Applied to files:**
- `packages/db/package.json`

</details>
<details>
<summary>📚 Learning: 2025-08-28T12:01:24.375Z</summary>

Learnt from: chakrihacker
Repo: fyndx/universal-starter PR: 13
File: apps/frontend/tsconfig.json:13-17
Timestamp: 2025-08-28T12:01:24.375Z
Learning: The project uses TypeScript project references between frontend and backend specifically for Eden Treaty type safety. The frontend references the backend via "references": [{"path": "../backend"}] in tsconfig.json to access exported App types for compile-time type inference, not runtime imports.


**Applied to files:**
- `packages/db/package.json`

</details>

</details><details>
<summary>🧬 Code graph analysis (4)</summary>

<details>
<summary>apps/api/src/plugins/auth-guards.ts (2)</summary><blockquote>

<details>
<summary>apps/api/src/plugins/auth.ts (1)</summary>

* `withAuth` (4-16)

</details>
<details>
<summary>apps/api/src/utils/format-response.ts (1)</summary>

* `formatErrorResponse` (82-104)

</details>

</blockquote></details>
<details>
<summary>apps/api/src/plugins/auth.ts (1)</summary><blockquote>

<details>
<summary>apps/api/src/lib/auth.ts (1)</summary>

* `auth` (9-67)

</details>

</blockquote></details>
<details>
<summary>apps/api/src/utils/format-response.ts (4)</summary><blockquote>

<details>
<summary>apps/api/src/schemas/response.ts (6)</summary>

* `SuccessOptions` (114-128)
* `ErrorOptions` (146-160)
* `EnvelopeSuccess` (334-334)
* `FormatErrorParams` (324-326)
* `ProblemInput` (259-259)
* `EnvelopeError` (333-333)

</details>
<details>
<summary>apps/api/src/schemas/api-spec/meta-data.ts (1)</summary>

* `Meta` (28-28)

</details>
<details>
<summary>apps/api/src/schemas/api-spec/envelope.ts (2)</summary>

* `EnvelopeSuccess` (53-61)
* `EnvelopeError` (63-71)

</details>
<details>
<summary>apps/api/src/schemas/api-spec/problem.ts (1)</summary>

* `Problem` (33-33)

</details>

</blockquote></details>
<details>
<summary>apps/api/src/schemas/response.ts (4)</summary><blockquote>

<details>
<summary>apps/api/src/schemas/api-spec/links.ts (1)</summary>

* `LinkMapSchema` (7-16)

</details>
<details>
<summary>apps/api/src/schemas/api-spec/ui.ts (1)</summary>

* `UISchema` (60-83)

</details>
<details>
<summary>apps/api/src/schemas/api-spec/meta-data.ts (1)</summary>

* `MetaSchema` (7-27)

</details>
<details>
<summary>apps/api/src/schemas/api-spec/problem.ts (1)</summary>

* `ProblemFieldIssueSchema` (8-15)

</details>

</blockquote></details>

</details><details>
<summary>🪛 dotenv-linter (4.0.0)</summary>

<details>
<summary>.env.example</summary>

[warning] 8-8: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

---

[warning] 20-20: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>

<details>
<summary>README.md</summary>

99-99: Bare URL used

(MD034, no-bare-urls)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)</summary>

* GitHub Check: Sourcery review

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>.gitignore (1)</summary><blockquote>

`11-11`: **LGTM!**

Adding `.env` to the ignore list is a good security practice, especially with the new `.env.example` template file added in this PR.

</blockquote></details>
<details>
<summary>apps/frontend/package.json (1)</summary><blockquote>

`19-19`: **LGTM! Better-auth dependencies now aligned with API.**

The better-auth package versions are now consistent across frontend and API (both at ^1.4.1), which improves maintainability and reduces version mismatch issues.




Also applies to: 48-48

</blockquote></details>
<details>
<summary>apps/api/src/plugins/auth.ts (1)</summary><blockquote>

`1-16`: **LGTM! Quote style consistency improved.**

The changes update quote style from single to double quotes for consistency across the codebase. The authentication logic remains unchanged.

</blockquote></details>
<details>
<summary>packages/db/package.json (2)</summary><blockquote>

`28-28`: **LGTM! dotenv addition supports new configuration approach.**

Adding `dotenv` as a dependency aligns with the new `prisma.config.ts` file that uses `dotenv/config` for environment variable management, as noted in the AI summary.

---

`20-20`: **Prisma v7 upgrade is properly implemented and compatible.**

Verification confirms:
- Prisma v7.0.0 is stable with no security vulnerabilities in prisma, @prisma/client, or @prisma/adapter-pg
- Codebase has been correctly migrated to v7: generator provider set to "prisma-client", output path configured, driver adapter (@prisma/adapter-pg) installed and properly instantiated with PrismaClient, and prisma.config.ts in place
- All major breaking changes have been addressed

</blockquote></details>
<details>
<summary>apps/api/package.json (1)</summary><blockquote>

`12-12`: Both package versions exist and are secure. Version 1.4.1 of @better-auth/expo exists, and version 1.4.1 of better-auth exists. No security advisories affect these versions—all reported vulnerabilities target older releases (pre-1.1.16 through 1.3.25).

</blockquote></details>
<details>
<summary>package.json (1)</summary><blockquote>

`27-27`: **turbo version 2.6.1 verified as valid with no security advisories.**

The version exists in the npm registry and has no known security vulnerabilities. Verification complete.

</blockquote></details>
<details>
<summary>apps/api/src/plugins/auth-guards.ts (1)</summary><blockquote>

`6-30`: **Standardized 401 problem envelope looks good**

The guard cleanly short-circuits on missing `session`/`user`, sets the 401 status, and returns a structured RFC7807-like envelope via `formatErrorResponse`, including `www-authenticate`. This is consistent with the new response schema utilities.

</blockquote></details>
<details>
<summary>apps/api/src/utils/format-response.ts (1)</summary><blockquote>

`17-41`: **Meta/envelope builders align well with schema types**

The updated helpers:

- Use a clear `defaultId` with a `crypto.randomUUID` path and a base‑36 fallback.
- Build `meta` via a single spread‑based object, respecting overrides and merging `meta` last.
- Normalize `problem.code` and `problem.instance` in `formatErrorResponse` based on the envelope code and `meta.requestId`.

The implementations of `formatSuccessResponse` and `formatErrorResponse` look consistent with the new `SuccessOptions` / `ErrorOptions` / `ProblemInput` types.




Also applies to: 53-69, 82-104

</blockquote></details>
<details>
<summary>apps/api/src/schemas/response.ts (1)</summary><blockquote>

`1-90`: **Centralized response/problem schemas look solid**

This module nicely centralizes:

- Shared response option fields via `BaseOptionsSchema`.
- Success vs error options (`SuccessOptionsSchema` / `ErrorOptionsSchema`) with clear TS overlays for `now` / `generateRequestId`.
- RFC7807‑style `ProblemInputSchema` with extensions for docs/support URLs and retry hints.
- Parameter schemas for the formatters and re‑exports of envelope types.

The structure aligns well with `format-response.ts` and should make envelope construction much more consistent across the API.




Also applies to: 166-259, 276-297, 332-335

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread .env.example
DATABASE_URL=postgres://postgres:supersecret@localhost:5432/universal-starter?sslmode=require
DATASET_NAME=universal-
EMAIL_FROM=noreply@yourapp.com
EMAIL_FROM_NAME=Your App Name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add quotes around the value with spaces.

The value "Your App Name" contains spaces and should be quoted to ensure proper parsing.

Apply this diff:

-EMAIL_FROM_NAME=Your App Name
+EMAIL_FROM_NAME="Your App Name"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EMAIL_FROM_NAME=Your App Name
EMAIL_FROM_NAME="Your App Name"
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 8-8: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

🤖 Prompt for AI Agents
In .env.example around line 8, the EMAIL_FROM_NAME value contains spaces and
must be quoted to ensure correct parsing by dotenv-style loaders; update the
line to wrap the value in double quotes (e.g. EMAIL_FROM_NAME="Your App Name")
so the value is read as a single string.

@@ -0,0 +1,15 @@
import "dotenv/config";
import { defineConfig, env } from "prisma/config";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused env import

env is imported from "prisma/config" but never used, which will trip linting and goes against the “no unused imports” guideline. Either switch back to env("DATABASE_URL") once the upstream issue is fixed, or drop the import for now.

-import { defineConfig, env } from "prisma/config";
+import { defineConfig } from "prisma/config";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { defineConfig, env } from "prisma/config";
import { defineConfig } from "prisma/config";
🤖 Prompt for AI Agents
In packages/db/prisma.config.ts around line 2, the import statement brings in
`env` from "prisma/config" but `env` is unused; remove `env` from the import
list (or revert to using env("DATABASE_URL") once the upstream bug is resolved)
so the file no longer contains an unused import and linting errors are avoided.

const { role } = user || {};
const hasRole = role && roles.includes(role);
new Elysia({ name: "requireRole" })
.use(withAuth())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Calling .use(withAuth()) before .use(requireAuth()) duplicates withAuth decoration because requireAuth() already applies withAuth, so Elysia will throw a "property already decorated" error when auth is registered twice; drop the extra .use(withAuth()) to avoid the crash. [logic error]

Severity Level: Minor ⚠️

Suggested change
.use(withAuth())
Why it matters? ⭐

requireAuth() already calls .use(withAuth()), so the extra .use(withAuth()) before it decorates auth twice on the same Elysia instance and will surface the "property already decorated" failure. Dropping the redundant decorator fixes a real runtime error tied directly to this PR diff.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/api/src/plugins/auth-guards.ts
**Line:** 34:34
**Comment:**
	*Logic Error: Calling `.use(withAuth())` before `.use(requireAuth())` duplicates `withAuth` decoration because `requireAuth()` already applies `withAuth`, so Elysia will throw a "property already decorated" error when `auth` is registered twice; drop the extra `.use(withAuth())` to avoid the crash.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines 9 to 10
async function getSession({ request: { headers } }) {
const result = await auth.api.getSession({ headers });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: auth.api.getSession expects to receive the original request object so it can read headers/cookies; by only passing { headers } the underlying call now sees request as undefined and will throw when it tries to access request.headers, preventing authentication from working. [type error]

Severity Level: Minor ⚠️

Suggested change
async function getSession({ request: { headers } }) {
const result = await auth.api.getSession({ headers });
async function getSession({ request }) {
const result = await auth.api.getSession({ request });
Why it matters? ⭐

The helper on auth.api actually walks through request.headers/cookies, so feeding it an object that only has headers leaves request undefined and will throw when it tries to dereference request.headers. Passing the entire request fixes a real runtime authentication bug rather than touching style.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/api/src/plugins/auth.ts
**Line:** 9:10
**Comment:**
	*Type Error: `auth.api.getSession` expects to receive the original `request` object so it can read headers/cookies; by only passing `{ headers }` the underlying call now sees `request` as undefined and will throw when it tries to access `request.headers`, preventing authentication from working.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +4 to +8
export default defineConfig({
datasource: {
// Github issue: https://github.com/prisma/prisma/issues/28590
// url: env("DATABASE_URL"),
url: process.env.DATABASE_URL as string,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: process.env.DATABASE_URL as string is allowed to return undefined when the environment variable is missing, yet it is passed straight into Prisma, so the application will throw an invalid URL error at startup instead of failing fast with a clear message; validate the variable before handing it to defineConfig. [logic error]

Severity Level: Minor ⚠️

Suggested change
export default defineConfig({
datasource: {
// Github issue: https://github.com/prisma/prisma/issues/28590
// url: env("DATABASE_URL"),
url: process.env.DATABASE_URL as string,
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error("DATABASE_URL must be defined");
}
export default defineConfig({
datasource: {
// Github issue: https://github.com/prisma/prisma/issues/28590
// url: env("DATABASE_URL"),
url: databaseUrl,
Why it matters? ⭐

Passing process.env.DATABASE_URL as string straight into Prisma lets the app crash with a vague "invalid URL" when the env var is missing; checking it up front and throwing a meaningful error keeps startup failures actionable and protects subsequent code that assumes the config exists.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** packages/db/prisma.config.ts
**Line:** 4:8
**Comment:**
	*Logic Error: `process.env.DATABASE_URL as string` is allowed to return `undefined` when the environment variable is missing, yet it is passed straight into Prisma, so the application will throw an invalid URL error at startup instead of failing fast with a clear message; validate the variable before handing it to `defineConfig`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

@codeant-ai

codeant-ai Bot commented Nov 22, 2025

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@chakrihacker
chakrihacker merged commit 41e8f63 into main Nov 22, 2025
3 of 5 checks passed
@chakrihacker
chakrihacker deleted the feat/add-middleware branch November 22, 2025 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Possible security concern Review effort 2/5 Review effort 3/5 size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant