Repository navigation
Refactor authentication guards and add response schemas - #26
Conversation
- 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 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 · |
Reviewer's GuideRefactors 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 guardsequenceDiagram
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
Sequence diagram for unauthorized role check with requireRolesequenceDiagram
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
Class diagram for typed response option and problem schemasclassDiagram
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
Flow diagram for building success and error envelopes with shared optionsflowchart 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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughEnvironment 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||||||||||
CI Feedback 🧐(Feedback updated until commit a175f9c)A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- In the auth guards, you're now setting
set.statusto string values ("Unauthorized"/"Forbidden") while also computing numeric codes viagetStatusCode; consider settingset.statusdirectly to the numeric status for consistency and to avoid relying on framework-specific string coercion. - In
packages/db/prisma.config.ts, since you're bypassingenv("DATABASE_URL")and usingprocess.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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| // url: env("DATABASE_URL"), | ||
| url: process.env.DATABASE_URL as string, |
There was a problem hiding this comment.
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.
| 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)}`; |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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.
- 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.
- 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
});- 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.
- 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.
- 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.
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||
There was a problem hiding this comment.
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 missingDATABASE_URLCasting
process.env.DATABASE_URLtostringhides the possibility of it beingundefined, 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‑applyingwithAuth()inrequireRole
requireRolecurrently does:
.use(withAuth()).use(requireAuth()), andrequireAuth()itself already calls.use(withAuth()).This means the auth plugin (and its
derivelogic) 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 fallbackThe
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 usecrypto.getRandomValueswhere 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 howproblemInstanceinErrorOptionsis intended to be used
ErrorOptionsSchemaadds aproblemInstancefield, andErrorOptionsexposes it at the type level, but the formatter (formatErrorResponse) currently only derivesproblem.instancefromproblem.instance ?? meta.requestIdand never consultsoptions.problemInstance.This can be confusing for callers who might expect setting
problemInstanceon options to influence the resulting problem.Consider one of:
Wire it through: have
formatErrorResponsepreferoptions.problemInstancewhenproblem.instanceis 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 -->
| 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 |
There was a problem hiding this comment.
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.
| 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"; | |||
There was a problem hiding this comment.
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.
| 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()) |
There was a problem hiding this comment.
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
| .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.| async function getSession({ request: { headers } }) { | ||
| const result = await auth.api.getSession({ headers }); |
There was a problem hiding this comment.
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
| 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.| export default defineConfig({ | ||
| datasource: { | ||
| // Github issue: https://github.com/prisma/prisma/issues/28590 | ||
| // url: env("DATABASE_URL"), | ||
| url: process.env.DATABASE_URL as string, |
There was a problem hiding this comment.
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
| 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 finished reviewing your PR. |
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
File Walkthrough
3 files
Refactored guards with improved formatting and error messagesNew comprehensive response schemas with detailed documentationRefactored to import types from response schema module1 files
Updated quote style for consistency2 files
New Prisma configuration with environment variable supportRemoved engineType and url configuration for external config2 files
New environment variables template for configurationAdded inspiration section with reference link4 files
Updated better-auth and expo dependencies to latest versionsUpdated better-auth and expo dependencies to latest versionsUpdated turbo dependency to version 2.6.1Updated Prisma to v7, added dotenv and pg types dependenciesCodeAnt-AI Description
Deliver consistent auth failure responses and response schemas
What Changed
.env.exampletemplate 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:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
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:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
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.