-
-
Notifications
You must be signed in to change notification settings - Fork 486
🚧 Rest API #2085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🚧 Rest API #2085
Conversation
WalkthroughAdds API-key authentication, a Hono-based /api/private/polls endpoint with validation and timezone-aware slot generation, new Prisma ApiKey model + migration and CLI, time-slot utilities with tests, OpenAPI schemas, and related tests and dependency updates. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant HonoAPI as Hono API (/api/private/polls)
participant Auth as API Key Auth
participant Validator as Zod Validator / OpenAPI
participant SlotGen as Slot Generator
participant DB as Prisma DB
participant Redis as Redis (optional)
Note over HonoAPI,Auth: New/changed interactions highlighted
Client->>HonoAPI: POST /api/private/polls (Bearer <key> + payload)
HonoAPI->>Auth: verifyToken(rawKey)
Auth->>DB: SELECT api_key by prefix
DB-->>Auth: api_key record
alt invalid / revoked / expired
Auth-->>Client: 401 / 403
else valid
Auth->>Redis: rate-limit check (optional)
alt rate limited
Redis-->>Client: 429
else allowed
HonoAPI->>Validator: validate payload
alt validation error
Validator-->>Client: 400
else valid
alt all-day dates flow
HonoAPI->>DB: create poll + date options (zero-duration)
else time-slot flow
HonoAPI->>SlotGen: generateTimeSlots(generator, timeZone)
SlotGen-->>HonoAPI: deduped slots
HonoAPI->>DB: create poll + slot options
end
DB-->>HonoAPI: poll created (ids)
Auth->>DB: async update last_used_at (waitUntil)
HonoAPI-->>Client: 201 Created (adminUrl, inviteUrl)
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/web/src/app/api/private/[...route]/route.ts (3)
63-69: Edge case: short API keys may cause unexpected prefix extraction.If
rawKeyis shorter than 12 characters and doesn't match the expected format,slice(0, 12)will return the entire key as the prefix, which may not match any stored prefix.Consider adding validation or returning early for malformed keys:
🔎 Suggested defensive check
const extractApiKeyPrefix = (rawKey: string) => { + if (rawKey.length < 12) { + return null; + } const parts = rawKey.split("_").filter(Boolean); if (parts.length >= 3 && parts[1]) { return parts[1]; } return rawKey.slice(0, 12); };Then handle the
nullcase inverifyTokento returnfalse.
304-322: Consider returning HTTP 201 for resource creation.REST conventions typically use
201 Createdfor successful resource creation rather than200 OK.🔎 Suggested change
responses: { - 200: { + 201: { description: "Successful response",And update the
c.json()calls to include the status code:- return c.json({ + return c.json({ data: { ... } - }); + }, 201);
353-357: DuplicatespaceMemberquery logic.The same query appears in both the dates and slots branches. Consider extracting it to reduce duplication.
🔎 Suggested refactor
Extract the query before the branching logic:
+ const spaceMember = await prisma.spaceMember.findFirst({ + where: { userId }, + orderBy: { lastSelectedAt: "desc" }, + select: { spaceId: true }, + }); + // Process dates (all-day options) if (input.dates) { // ... date processing ... - const spaceMember = await prisma.spaceMember.findFirst({ - where: { userId }, - orderBy: { lastSelectedAt: "desc" }, - select: { spaceId: true }, - }); // ... poll creation ... }Also applies to: 452-456
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
apps/web/package.jsonapps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/src/utils/supported-time-zones.tspackages/database/package.jsonpackages/database/prisma/migrations/20251222101400_add_api_keys/migration.sqlpackages/database/prisma/models/user.prismapackages/database/prisma/scripts/create-api-key.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*
📄 CodeRabbit inference engine (.cursorrules)
Always use kebab-case for file names
Files:
packages/database/prisma/models/user.prismaapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tspackages/database/prisma/scripts/create-api-key.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/package.jsonapps/web/src/utils/supported-time-zones.tspackages/database/package.jsonpackages/database/prisma/migrations/20251222101400_add_api_keys/migration.sql
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Use dayjs for date handling
Use react-query for data fetching
Prefer implicit return values over explicit return values
Use zod for form validation
Create separate import statements for types
Prefer using the React module APIs (e.g. React.useState) instead of standalone hooks (e.g. useState)
Prefer double quotes for strings over single quotes
Only add comments when it is necessary to explain code that isn't self-explanatory
**/*.{ts,tsx}: Only create named interfaces when they're reused or complex
When TypeScript errors occur for missing i18n keys, runpnpm i18n:scaninstead of manually adding keys
Files:
apps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tspackages/database/prisma/scripts/create-api-key.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/src/utils/supported-time-zones.ts
**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,json}: i18n keys are in camelCase
i18nKeys should describe the message in camelCase. Ex. "lastUpdated": "Last Updated"
If the i18nKey is not intended to be reused, prefix it with the component name in camelCase
Files:
apps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tspackages/database/prisma/scripts/create-api-key.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/package.jsonapps/web/src/utils/supported-time-zones.tspackages/database/package.json
**/*.ts
📄 CodeRabbit inference engine (.cursorrules)
On the server use the
getTranslationsfunction from @/i18n/server to get the translations
Files:
apps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tspackages/database/prisma/scripts/create-api-key.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/src/utils/supported-time-zones.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Biome for code formatting with indent of 2 spaces and double quotes
Files:
apps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tspackages/database/prisma/scripts/create-api-key.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/src/utils/supported-time-zones.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{tsx,ts}: Prefer inline prop types over named interfaces for simple component props (e.g.,function Component({ prop }: { prop: string })instead of defining a separate interface)
Always use theuseDialoghook from@rallly/ui/dialogfor managing dialog state instead of manualuseStatefor open/close state
Use TanStack Query with tRPC for server state management
Use React Context for client state (auth, preferences, etc.)
Use react-hook-form with Zod validation for form state management
Use TailwindCSS with custom design system for styling
Files:
apps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tspackages/database/prisma/scripts/create-api-key.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/src/utils/supported-time-zones.ts
apps/web/src/app/**
📄 CodeRabbit inference engine (CLAUDE.md)
Use Next.js App Router conventions for route handlers
Files:
apps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/app/api/private/utils/time-slots.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/.cursor/rules/better-auth.md)
apps/web/**/*.{ts,tsx,js,jsx}: When using the username plugin, require all sign up endpoints to accept ausername(used for login, normalized) and an optionaldisplayUsername(raw, for display purposes).
When using the username plugin, all username values must be normalized according to the configuration function before storage or comparison (default: lowercase).
Whenever updating a user's username, always check for uniqueness and apply the normalization procedure.
Login endpoints or forms supporting username authentication must allow signing in with username and password, not just email.
Files:
apps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/app/api/private/utils/time-slots.tsapps/web/src/utils/supported-time-zones.ts
🧠 Learnings (6)
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.{ts,tsx} : Use dayjs for date handling
Applied to files:
apps/web/src/app/api/private/utils/time-slots.ts
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Rallly is built with Next.js 15, React 19, tRPC, Prisma with PostgreSQL, NextAuth.js, TailwindCSS, and TypeScript throughout
Applied to files:
apps/web/package.json
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.{tsx,css,config.ts} : Use tailwindcss for styling
Applied to files:
apps/web/package.json
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Applies to **/*.{tsx,ts} : Use TailwindCSS with custom design system for styling
Applied to files:
apps/web/package.json
📚 Learning: 2025-11-25T11:04:24.541Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: apps/web/.cursor/rules/better-auth.md:0-0
Timestamp: 2025-11-25T11:04:24.541Z
Learning: Applies to apps/web/**/auth.ts : When extending your schema via the plugin, migrate or regenerate your database schema (using CLI) so the new fields are present and unique.
Applied to files:
packages/database/package.json
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Applies to **/*.{ts,tsx} : When TypeScript errors occur for missing i18n keys, run `pnpm i18n:scan` instead of manually adding keys
Applied to files:
packages/database/package.json
🧬 Code graph analysis (2)
apps/web/src/app/api/private/[...route]/route.test.ts (2)
packages/database/index.ts (1)
prisma(18-18)apps/web/src/app/api/private/[...route]/route.ts (1)
app(484-484)
packages/database/prisma/scripts/create-api-key.ts (2)
packages/database/index.ts (1)
prisma(18-18)apps/web/src/trpc/routers/user.ts (1)
user(18-205)
⏰ 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)
- GitHub Check: Integration tests
🔇 Additional comments (16)
packages/database/package.json (1)
11-11: LGTM!The new
db:create-api-keyscript follows the existing naming convention and uses the already-availabletsxrunner.apps/web/src/utils/supported-time-zones.ts (1)
431-436: LGTM!Clean implementation using the type assertion to satisfy TypeScript's strict
includes()typing with readonly tuple types.packages/database/prisma/migrations/20251222101400_add_api_keys/migration.sql (1)
1-33: Well-designed API key schema.The migration includes appropriate indexes for common access patterns:
- Unique prefix for O(1) key lookup
- User index for listing keys per user
- Expiry/revocation indexes for filtering active keys
- Composite unique on
(user_id, name)to prevent duplicate key namesCASCADE delete on the foreign key ensures proper cleanup when users are removed.
packages/database/prisma/models/user.prisma (1)
68-68: LGTM!The
ApiKeymodel is well-structured with:
- Proper field mappings to snake_case database columns
- Cascade delete maintaining referential integrity
- Named indexes matching the migration exactly
@db.Textfor the hashed key storageAlso applies to: 91-112
apps/web/src/app/api/private/utils/time-slots.test.ts (1)
1-223: Comprehensive test coverage for time-slot utilities.The test suite covers:
- Timezone parsing with various offset formats
- Deduplication logic including edge cases (same time, different duration)
- Slot generation across date ranges with day-of-week filtering
- Edge cases like invalid time ranges and duration exceeding windows
Well-structured tests following the AAA pattern.
apps/web/src/app/api/private/[...route]/route.test.ts (2)
43-52: Test helper uses simplified hash - may need alignment with production.The
createMockApiKeyhelper uses a simple SHA-256 hash without salt:const hashedKey = crypto.createHash("sha256").update(rawKey).digest("hex");This works for testing if the route's verification logic also uses simple hashing, but conflicts with the salted format in
create-api-key.ts. Ensure the route handler and this test helper use consistent hashing logic.
72-456: Excellent test coverage for the private API route.The test suite thoroughly validates:
- Authentication flows (missing/invalid/revoked/expired keys)
- Poll creation with dates and time slots
- Timezone handling and fallback logic
- Input validation (required fields, mutual exclusivity of dates/slots)
- OpenAPI spec and docs endpoints
The mock setup properly isolates the handler from external dependencies.
packages/database/prisma/scripts/create-api-key.ts (1)
40-72: The verification logic inroute.tscorrectly handles the salted hash format (sha256$salt$hash) used by the creation script. No format mismatch exists.Likely an incorrect or invalid review comment.
apps/web/package.json (1)
28-29: LGTM - New Hono ecosystem dependencies for OpenAPI support.The added packages (
@hono/standard-validator,@hono/zod-openapi,@scalar/hono-api-reference,hono-openapi) align well with the new private API infrastructure. All versions are available and compatible with the existing hono 4.11.1 setup.apps/web/src/app/api/private/[...route]/route.ts (3)
78-95: LGTM!The API key verification correctly uses
crypto.timingSafeEqualto prevent timing attacks, and properly handles both salted (sha256$salt$hash) and unsalted hash formats.
384-389: Defensive check is acceptable but redundant.The zod schema refinements at lines 218-223 already guarantee that either
datesorslotsis provided (but not both). This runtime check is technically unreachable if validation passes, but serves as a safety net.
286-297: LGTM!The rate limiter is correctly configured with per-API-key limiting and optional Redis backing when KV is enabled.
apps/web/src/app/api/private/utils/time-slots.ts (4)
1-8: LGTM!Correctly uses dayjs with required plugins for timezone handling as per coding guidelines.
24-30: LGTM!Efficient deduplication using a composite key of timestamp and duration. Map preserves insertion order for deterministic results.
32-42: LGTM!The timezone handling is correct. Using
dayjs(value).tz(timeZone, true)properly interprets the datetime as local to the specified timezone without conversion.
53-131: LGTM!The slot generation logic correctly:
- Maps day-of-week names to JavaScript day indices
- Validates that
toTimeis afterfromTime- Iterates through the date range filtering by allowed days
- Generates slots at the specified interval within each day's time window
- Deduplicates results before returning
The use of
dayjs.tz()should handle DST transitions correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/web/src/app/api/private/utils/time-slots.test.ts (1)
208-223: Optional: Consider removing slightly redundant deduplication test.This test verifies that generated slots are deduplicated by comparing array length with unique time count. However, deduplication is already thoroughly tested in the
dedupeTimeSlotstest suite (lines 53-83), and the implementation callsdedupeTimeSlotsdirectly (as shown in the relevant code snippet from apps/web/src/app/api/private/utils/time-slots.ts:52-130).Rationale
The deduplication behavior is:
- Already unit-tested in the dedicated
dedupeTimeSlotssuite- Inherently verified by the implementation calling
dedupeTimeSlots- This test adds minimal additional value beyond verifying the function is called
You could remove this test to reduce redundancy, or keep it as an integration check that generation + deduplication work together.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/app/api/private/utils/time-slots.test.tsapps/web/src/components/forms/poll-details-form.tsx
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Use dayjs for date handling
Use react-query for data fetching
Prefer implicit return values over explicit return values
Use zod for form validation
Create separate import statements for types
Prefer using the React module APIs (e.g. React.useState) instead of standalone hooks (e.g. useState)
Prefer double quotes for strings over single quotes
Only add comments when it is necessary to explain code that isn't self-explanatory
**/*.{ts,tsx}: Only create named interfaces when they're reused or complex
When TypeScript errors occur for missing i18n keys, runpnpm i18n:scaninstead of manually adding keys
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/components/forms/poll-details-form.tsxapps/web/src/app/api/private/utils/time-slots.test.ts
**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,json}: i18n keys are in camelCase
i18nKeys should describe the message in camelCase. Ex. "lastUpdated": "Last Updated"
If the i18nKey is not intended to be reused, prefix it with the component name in camelCase
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/components/forms/poll-details-form.tsxapps/web/src/app/api/private/utils/time-slots.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursorrules)
On the server use the
getTranslationsfunction from @/i18n/server to get the translations
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/app/api/private/utils/time-slots.test.ts
**/*
📄 CodeRabbit inference engine (.cursorrules)
Always use kebab-case for file names
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/components/forms/poll-details-form.tsxapps/web/src/app/api/private/utils/time-slots.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Biome for code formatting with indent of 2 spaces and double quotes
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/components/forms/poll-details-form.tsxapps/web/src/app/api/private/utils/time-slots.test.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{tsx,ts}: Prefer inline prop types over named interfaces for simple component props (e.g.,function Component({ prop }: { prop: string })instead of defining a separate interface)
Always use theuseDialoghook from@rallly/ui/dialogfor managing dialog state instead of manualuseStatefor open/close state
Use TanStack Query with tRPC for server state management
Use React Context for client state (auth, preferences, etc.)
Use react-hook-form with Zod validation for form state management
Use TailwindCSS with custom design system for styling
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/components/forms/poll-details-form.tsxapps/web/src/app/api/private/utils/time-slots.test.ts
apps/web/src/app/**
📄 CodeRabbit inference engine (CLAUDE.md)
Use Next.js App Router conventions for route handlers
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/app/api/private/utils/time-slots.test.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/.cursor/rules/better-auth.md)
apps/web/**/*.{ts,tsx,js,jsx}: When using the username plugin, require all sign up endpoints to accept ausername(used for login, normalized) and an optionaldisplayUsername(raw, for display purposes).
When using the username plugin, all username values must be normalized according to the configuration function before storage or comparison (default: lowercase).
Whenever updating a user's username, always check for uniqueness and apply the normalization procedure.
Login endpoints or forms supporting username authentication must allow signing in with username and password, not just email.
Files:
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/components/forms/poll-details-form.tsxapps/web/src/app/api/private/utils/time-slots.test.ts
**/*.{tsx,css,config.ts}
📄 CodeRabbit inference engine (.cursorrules)
Use tailwindcss for styling
Files:
apps/web/src/components/forms/poll-details-form.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use react-hook-form for form handling
All text in the UI should be translated using either the Trans component or the useTranslation hook
Prefer composable components in the style of shadcn UI over large monolithic components
DropdownMenuItem is a flex container with a preset gap so there is no need to add margins to the children
The size and colour of an icon should be set by wrapping it with the component from @rallly/ui/icon which will give it the correct colour and size
Keep the props of a component as minimal as possible. Pass only the bare minimum amount of information needed to it
All text in the UI should be translatable
Use the component in client components from @/components/trans with thedefaultsprop to provide the default text
Always use a composable patterns when building components
Usecn()from @rallly/ui to compose classes
Add the "use client" directive to the top of any .tsx file that requires client-side javascript
Files:
apps/web/src/components/forms/poll-details-form.tsx
apps/web/src/components/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place shared components in
apps/web/src/components/
Files:
apps/web/src/components/forms/poll-details-form.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Applies to **/*.{tsx,ts} : Use react-hook-form with Zod validation for form state management
Applied to files:
apps/web/src/components/forms/poll-details-form.tsx
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.tsx : Use react-hook-form for form handling
Applied to files:
apps/web/src/components/forms/poll-details-form.tsx
🧬 Code graph analysis (2)
apps/web/src/app/api/private/[...route]/route.test.ts (2)
packages/database/index.ts (1)
prisma(18-18)apps/web/src/app/api/private/[...route]/route.ts (1)
app(484-484)
apps/web/src/app/api/private/utils/time-slots.test.ts (1)
apps/web/src/app/api/private/utils/time-slots.ts (3)
parseStartTime(44-51)dedupeTimeSlots(24-30)generateTimeSlots(53-131)
⏰ 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)
- GitHub Check: Integration tests
🔇 Additional comments (3)
apps/web/src/components/forms/poll-details-form.tsx (1)
23-23: LGTM - Clean removal of unused destructured value.The
formState.errorswas not being used in the component. Error display is properly handled by theFormMessagecomponent within eachFormField.apps/web/src/app/api/private/utils/time-slots.test.ts (1)
1-225: LGTM - Comprehensive test coverage for time-slot utilities.The test suite thoroughly covers all three utility functions with excellent edge case coverage, including:
- Timezone offset handling (positive, negative, with/without offset)
- Deduplication logic (exact duplicates, different durations, empty arrays)
- Slot generation across various scenarios (single/multiple days, custom intervals, timezone handling, validation edge cases)
All tests are well-structured and follow good testing practices.
apps/web/src/app/api/private/[...route]/route.test.ts (1)
1-477: LGTM - Excellent comprehensive test coverage for the private API route.The test suite is exceptionally well-structured with 18 test cases covering:
- Authentication flows (lines 77-150): Missing/invalid/revoked/expired API keys
- Date-based poll creation (lines 152-259): Basic creation, deduplication, limits validation
- Slot-based poll creation (lines 261-390): Time slots, timezone handling, slot generators
- Validation (lines 392-459): Input validation edge cases
- OpenAPI (lines 461-476): Spec and docs endpoints
The test setup properly mocks all external dependencies (Prisma, URLs, KV, etc.) and verifies both response structure and database call arguments using
expect.objectContaining, which provides good coverage without being overly brittle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
apps/web/src/app/api/private/utils/api-key.ts (1)
6-12: Consider documenting the expected key format.The function handles two cases: structured keys like
rallly_abc123_secretkey(extracting "abc123") and plain keys (taking first 12 chars). Adding a brief comment describing the expected format would improve maintainability.apps/web/src/app/api/private/schemas.ts (1)
9-12: Clarify time format in description.The description says "HH:mm (24-hour) format" but
z.iso.time()accepts full ISO 8601 time strings (e.g.,09:30:00or09:30:00.000). Update the description to match actual validation behavior, or restrict the format if HH:mm is intended.🔎 Proposed fix
export const timeSchema = z.iso.time().openapi({ - description: "Time in HH:mm (24-hour) format", + description: "Time in HH:mm or HH:mm:ss (24-hour) format", example: "09:30", });apps/web/src/app/api/private/utils/poll.ts (1)
19-55: Clean poll creation implementation.The function properly:
- Finds the user's most recently selected space
- Creates the poll with all necessary fields including location
- Sets up the watcher relationship
- Returns the constructed URLs
One observation:
title,location, anddescriptionare selected but not used in the return value. Consider removing unused fields from the select clause.🔎 Optional: Simplify select clause
select: { id: true, title: true, location: true, description: true }, + // Could simplify to: + select: { id: true },
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/src/app/api/private/[...route]/route.test.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Use dayjs for date handling
Use react-query for data fetching
Prefer implicit return values over explicit return values
Use zod for form validation
Create separate import statements for types
Prefer using the React module APIs (e.g. React.useState) instead of standalone hooks (e.g. useState)
Prefer double quotes for strings over single quotes
Only add comments when it is necessary to explain code that isn't self-explanatory
**/*.{ts,tsx}: Only create named interfaces when they're reused or complex
When TypeScript errors occur for missing i18n keys, runpnpm i18n:scaninstead of manually adding keys
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,json}: i18n keys are in camelCase
i18nKeys should describe the message in camelCase. Ex. "lastUpdated": "Last Updated"
If the i18nKey is not intended to be reused, prefix it with the component name in camelCase
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursorrules)
On the server use the
getTranslationsfunction from @/i18n/server to get the translations
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
**/*
📄 CodeRabbit inference engine (.cursorrules)
Always use kebab-case for file names
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Biome for code formatting with indent of 2 spaces and double quotes
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{tsx,ts}: Prefer inline prop types over named interfaces for simple component props (e.g.,function Component({ prop }: { prop: string })instead of defining a separate interface)
Always use theuseDialoghook from@rallly/ui/dialogfor managing dialog state instead of manualuseStatefor open/close state
Use TanStack Query with tRPC for server state management
Use React Context for client state (auth, preferences, etc.)
Use react-hook-form with Zod validation for form state management
Use TailwindCSS with custom design system for styling
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
apps/web/src/app/**
📄 CodeRabbit inference engine (CLAUDE.md)
Use Next.js App Router conventions for route handlers
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/.cursor/rules/better-auth.md)
apps/web/**/*.{ts,tsx,js,jsx}: When using the username plugin, require all sign up endpoints to accept ausername(used for login, normalized) and an optionaldisplayUsername(raw, for display purposes).
When using the username plugin, all username values must be normalized according to the configuration function before storage or comparison (default: lowercase).
Whenever updating a user's username, always check for uniqueness and apply the normalization procedure.
Login endpoints or forms supporting username authentication must allow signing in with username and password, not just email.
Files:
apps/web/src/app/api/private/schemas.tsapps/web/src/app/api/private/utils/api-key.tsapps/web/src/app/api/private/utils/poll.tsapps/web/src/app/api/private/[...route]/route.tsapps/web/src/app/api/private/[...route]/route.test.ts
🧠 Learnings (1)
📚 Learning: 2025-11-25T11:04:24.541Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: apps/web/.cursor/rules/better-auth.md:0-0
Timestamp: 2025-11-25T11:04:24.541Z
Learning: Applies to apps/web/**/auth.ts : When extending your schema via the plugin, migrate or regenerate your database schema (using CLI) so the new fields are present and unique.
Applied to files:
apps/web/src/app/api/private/schemas.ts
🧬 Code graph analysis (4)
apps/web/src/app/api/private/schemas.ts (1)
apps/web/src/utils/supported-time-zones.ts (1)
isSupportedTimeZone(432-436)
apps/web/src/app/api/private/utils/api-key.ts (1)
packages/database/index.ts (1)
prisma(18-18)
apps/web/src/app/api/private/[...route]/route.ts (6)
apps/web/src/app/api/private/utils/api-key.ts (1)
apiKeyAuth(40-85)apps/web/src/lib/kv.ts (2)
isKvEnabled(5-5)kv(5-5)apps/web/src/app/api/private/schemas.ts (3)
createPollSuccessResponseSchema(96-108)errorResponseSchema(110-120)createPollInputSchema(70-94)apps/web/src/app/api/private/utils/poll.ts (2)
apiError(57-59)createPoll(19-55)apps/web/src/utils/supported-time-zones.ts (1)
isSupportedTimeZone(432-436)apps/web/src/app/api/private/utils/time-slots.ts (4)
parseStartTime(44-51)SlotGeneratorInput(10-17)generateTimeSlots(53-131)dedupeTimeSlots(24-30)
apps/web/src/app/api/private/[...route]/route.test.ts (2)
packages/database/index.ts (1)
prisma(18-18)apps/web/src/app/api/private/[...route]/route.ts (1)
app(238-238)
🪛 GitHub Check: Unit tests
apps/web/src/app/api/private/[...route]/route.test.ts
[failure] 168-168: src/app/api/private/[...route]/route.test.ts > Private API - /polls > Create poll with dates > should create a poll with date options
TypeError: Cannot read properties of undefined (reading 'id')
❯ src/app/api/private/[...route]/route.test.ts:168:24
🪛 Gitleaks (8.30.0)
apps/web/src/app/api/private/[...route]/route.test.ts
[high] 60-60: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ 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)
- GitHub Check: Integration tests
🔇 Additional comments (18)
apps/web/src/app/api/private/utils/api-key.ts (3)
1-4: LGTM!The imports are appropriate for the API key authentication implementation, using Node's crypto for hashing, Prisma for database access, Vercel's waitUntil for async operations, and Hono's bearerAuth middleware.
21-38: Good security implementation with timing-safe comparison.The
verifyApiKeyfunction properly usescrypto.timingSafeEqualto prevent timing attacks. The support for both salted (sha256$salt$hash) and plain hex formats provides flexibility.
40-85: Well-structured authentication flow.The verifyToken implementation properly validates the API key existence, revocation status, expiration, and hash before granting access. The async
lastUsedAtupdate viawaitUntilavoids blocking the request.apps/web/src/app/api/private/[...route]/route.test.ts (4)
59-61: Test API key is acceptable for testing purposes.The Gitleaks warning for the test API key
rallly_abc123_secretkeyis a false positive since this is clearly test fixture data with no real security implications.
77-149: Comprehensive authentication test coverage.The authentication tests properly cover missing headers, invalid keys, revoked keys, and expired keys. Good edge case coverage.
237-260: Good test for location field persistence.This test verifies the location field is saved, which aligns with the fix mentioned in past review comments. Ensure the route implementation includes
locationin the createPoll call (which it now does based on the relevant code snippets).
486-501: OpenAPI endpoint tests are appropriate.Tests verify the spec endpoint returns correct metadata and the docs page returns HTML content.
apps/web/src/app/api/private/schemas.ts (3)
14-26: Well-structured slot generator schema.The schema properly defines date ranges, allowed days, time windows, and optional interval with sensible bounds (15-1440 minutes).
70-94: Good use of refinements for mutual exclusivity.The schema correctly enforces that exactly one of
datesorslotsmust be provided. Clear error messages for both validation cases.
96-120: Response schemas are well-defined for OpenAPI documentation.Both success and error response schemas include appropriate examples for API documentation.
apps/web/src/app/api/private/utils/poll.ts (2)
1-4: LGTM!Appropriate imports for database access, URL generation, and ID creation.
57-59: Simple and effective error helper.The
apiErrorfunction provides a consistent structure for API error responses.apps/web/src/app/api/private/[...route]/route.ts (6)
1-27: LGTM!Comprehensive imports for the route implementation including rate limiting, OpenAPI support, validation, and utilities.
42-70: Well-configured OpenAPI and documentation endpoints.The OpenAPI spec includes proper security scheme configuration, and the Scalar docs provide a nice UI for API exploration.
72-83: Good rate limiting implementation.Rate limiting is keyed per API key ID and uses Redis when available, falling back gracefully when KV is not enabled.
148-177: Robust timezone validation flow.Good handling of timezone requirements: falls back to user's timezone, validates against supported timezones, and returns clear error messages when timezone is missing or invalid.
179-217: Well-implemented slot generation and validation.The code properly:
- Handles both string times and slot generator objects
- Deduplicates generated slots
- Enforces MAX_OPTIONS limit with clear error message
- Returns appropriate error when no options are generated
238-241: LGTM!Proper exports for the Hono app and Vercel-compatible handlers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/app/api/private/[...route]/route.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Use dayjs for date handling
Use react-query for data fetching
Prefer implicit return values over explicit return values
Use zod for form validation
Create separate import statements for types
Prefer using the React module APIs (e.g. React.useState) instead of standalone hooks (e.g. useState)
Prefer double quotes for strings over single quotes
Only add comments when it is necessary to explain code that isn't self-explanatory
**/*.{ts,tsx}: Only create named interfaces when they're reused or complex
When TypeScript errors occur for missing i18n keys, runpnpm i18n:scaninstead of manually adding keys
Files:
apps/web/src/app/api/private/[...route]/route.ts
**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,json}: i18n keys are in camelCase
i18nKeys should describe the message in camelCase. Ex. "lastUpdated": "Last Updated"
If the i18nKey is not intended to be reused, prefix it with the component name in camelCase
Files:
apps/web/src/app/api/private/[...route]/route.ts
**/*.ts
📄 CodeRabbit inference engine (.cursorrules)
On the server use the
getTranslationsfunction from @/i18n/server to get the translations
Files:
apps/web/src/app/api/private/[...route]/route.ts
**/*
📄 CodeRabbit inference engine (.cursorrules)
Always use kebab-case for file names
Files:
apps/web/src/app/api/private/[...route]/route.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Biome for code formatting with indent of 2 spaces and double quotes
Files:
apps/web/src/app/api/private/[...route]/route.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{tsx,ts}: Prefer inline prop types over named interfaces for simple component props (e.g.,function Component({ prop }: { prop: string })instead of defining a separate interface)
Always use theuseDialoghook from@rallly/ui/dialogfor managing dialog state instead of manualuseStatefor open/close state
Use TanStack Query with tRPC for server state management
Use React Context for client state (auth, preferences, etc.)
Use react-hook-form with Zod validation for form state management
Use TailwindCSS with custom design system for styling
Files:
apps/web/src/app/api/private/[...route]/route.ts
apps/web/src/app/**
📄 CodeRabbit inference engine (CLAUDE.md)
Use Next.js App Router conventions for route handlers
Files:
apps/web/src/app/api/private/[...route]/route.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/.cursor/rules/better-auth.md)
apps/web/**/*.{ts,tsx,js,jsx}: When using the username plugin, require all sign up endpoints to accept ausername(used for login, normalized) and an optionaldisplayUsername(raw, for display purposes).
When using the username plugin, all username values must be normalized according to the configuration function before storage or comparison (default: lowercase).
Whenever updating a user's username, always check for uniqueness and apply the normalization procedure.
Login endpoints or forms supporting username authentication must allow signing in with username and password, not just email.
Files:
apps/web/src/app/api/private/[...route]/route.ts
⏰ 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)
- GitHub Check: Integration tests
🔇 Additional comments (15)
apps/web/src/app/api/private/[...route]/route.ts (15)
1-27: LGTM! Imports are well-organized.The imports follow the coding guidelines with separate type imports and consistent use of double quotes.
29-36: LGTM! Type definition is clear and appropriate.The inline Env type properly defines the Hono context variables structure.
38-40: LGTM! App initialization is correct.The Hono app is properly typed and configured with an appropriate basePath.
42-61: LGTM! OpenAPI endpoint is properly configured.The OpenAPI documentation endpoint is set up with appropriate metadata and security scheme definitions.
63-70: LGTM! Documentation UI endpoint is properly configured.The Scalar documentation endpoint correctly references the OpenAPI specification.
72-84: LGTM! Middleware configuration is well-structured.The authentication and rate limiting middleware are properly configured with appropriate limits and key generation strategy.
84-109: LGTM! Route documentation is comprehensive.The OpenAPI route description properly documents the endpoint with appropriate schemas and security requirements.
109-118: LGTM! Input validation and user lookup are properly implemented.The handler correctly validates input and efficiently queries for the user's timezone.
127-135: LGTM! Options limit validation is clear and appropriate.The MAX_OPTIONS check provides helpful error messaging with actual counts.
137-152: LGTM! Poll creation and response structure are correct.The dates flow properly creates the poll with all required fields and returns a correctly structured response.
155-183: LGTM! Slots validation logic is comprehensive.The timezone resolution, fallback handling, and validation are properly implemented with clear error messages.
185-203: LGTM! Slot generation logic is well-structured.The code properly handles both explicit time strings and slot generator objects, with appropriate deduplication.
205-223: LGTM! Options validation covers important edge cases.The checks for empty options and excessive options are appropriate with helpful error messages.
225-241: LGTM! Slots flow poll creation is properly implemented.The slots flow correctly includes timezone metadata and returns a properly structured response.
244-247: LGTM! Exports follow Next.js App Router conventions.The module exports are correctly structured for Vercel deployment and Next.js App Router integration.
| const options = uniqueDates.map((date) => ({ | ||
| startTime: new Date(`${date}T00:00:00.000Z`), | ||
| duration: 0, | ||
| })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Use dayjs for date handling per coding guidelines.
The coding guidelines specify "Use dayjs for date handling" for TypeScript files, but this code uses new Date() directly. While the current implementation works, it should use dayjs for consistency.
Based on coding guidelines for **/*.{ts,tsx} files.
🔎 Proposed refactor using dayjs
+import dayjs from "dayjs";
+import utc from "dayjs/plugin/utc";
+
+dayjs.extend(utc);Then update the mapping:
- const options = uniqueDates.map((date) => ({
- startTime: new Date(`${date}T00:00:00.000Z`),
- duration: 0,
- }));
+ const options = uniqueDates.map((date) => ({
+ startTime: dayjs.utc(date).startOf("day").toDate(),
+ duration: 0,
+ }));Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/web/src/app/api/private/[...route]/route.ts around lines 122 to 125, the
mapping builds Date objects with new Date(...) which violates the guideline to
use dayjs; replace this by using dayjs to parse the date and produce the UTC
midnight Date (e.g. dayjs(date).utc().startOf('day').toDate()), and ensure you
import dayjs and the utc plugin and call dayjs.extend(utc) at the module scope
if not already done so.
Summary by CodeRabbit
New Features
Chores
Tests
✏️ Tip: You can customize this high-level summary in your review settings.