Skip to content

Conversation

@lukevella
Copy link
Owner

@lukevella lukevella commented Dec 23, 2025

Summary by CodeRabbit

  • New Features

    • Authenticated private API for creating polls (date or time-slot flows), OpenAPI/docs endpoints, timezone validation, slot generation/deduplication, and admin/invite URLs.
    • API key authentication middleware with usage tracking.
  • Chores

    • Database migration and model for API keys; CLI tool to create API keys.
    • Added runtime dependencies for validation and OpenAPI generation.
  • Tests

    • Extensive unit and integration tests for poll flows, auth scenarios, time-slot utilities, and OpenAPI endpoints.

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

@vercel
Copy link

vercel bot commented Dec 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
app Ready Ready Preview, Comment Dec 26, 2025 9:45am
1 Skipped Deployment
Project Deployment Review Updated (UTC)
landing Skipped Skipped Dec 26, 2025 9:45am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Dependencies
apps/web/package.json
Added OpenAPI/validator/Hono dependencies: @hono/standard-validator, @hono/zod-openapi, @scalar/hono-api-reference, hono-openapi.
API route & handlers
apps/web/src/app/api/private/[...route]/route.ts
New Hono app exported as app, plus Vercel GET/POST handlers; POST /polls implements API-key auth, optional Redis rate-limiting, Zod validation/OpenAPI endpoints, and dual poll creation flows (dates or generated slots).
API route tests
apps/web/src/app/api/private/[...route]/route.test.ts
New comprehensive Vitest suite covering auth, validation, poll creation flows, timezone behavior, slot generation, and OpenAPI endpoints.
API key auth util
apps/web/src/app/api/private/utils/api-key.ts
New apiKeyAuth (bearerAuth) with prefix parsing, salted/plain SHA-256 verification, expiry/revocation checks, ctx population, and async last-used update.
Poll creation util
apps/web/src/app/api/private/utils/poll.ts
New createPoll and apiError: resolves user spaceId, generates IDs, bulk creates poll + options, constructs admin/invite URLs.
Schemas (Zod + OpenAPI)
apps/web/src/app/api/private/schemas.ts
New Zod schemas with OpenAPI metadata: date/time/slotGenerator, createPoll input, success and error responses, and mutual-exclusivity validation.
Time-slot utilities & tests
apps/web/src/app/api/private/utils/time-slots.ts, apps/web/src/app/api/private/utils/time-slots.test.ts
New timezone-aware parsing, slot generation, deduplication utilities and unit tests covering offsets, intervals, multi-day windows, validation, and dedupe logic.
Supported time zones helper
apps/web/src/utils/supported-time-zones.ts
Added isSupportedTimeZone(timeZone: string) export to validate time zone strings.
Database schema, migration & tooling
packages/database/prisma/models/user.prisma, packages/database/prisma/migrations/..._add_api_keys/migration.sql, packages/database/prisma/scripts/create-api-key.ts, packages/database/package.json
New Prisma ApiKey model and api_keys migration (indices, FK to users); added User.apiKeys relation; new TS CLI script to create API keys; npm script db:create-api-key.
Minor UI change
apps/web/src/components/forms/poll-details-form.tsx
Removed unused formState.errors destructuring; only register is used now.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰
Keys stitched, hashed, and tucked away,
Time slots sprout where zones can play,
Polls now bloom with URLs neat,
I hop and clap my tiny feet,
Backend carrots — crunchy, hooray!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title is vague and generic, using only 'Rest API' with a construction emoji, providing minimal insight into the scope or specific purpose of the extensive changes across multiple files and systems. Replace with a more descriptive title that highlights the primary change, such as 'Add private API route for poll creation with API key authentication and time slot generation' or a similar concise summary of the main functionality.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rest-api

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 rawKey is 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 null case in verifyToken to return false.


304-322: Consider returning HTTP 201 for resource creation.

REST conventions typically use 201 Created for successful resource creation rather than 200 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: Duplicate spaceMember query 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5dfa419 and bae6fc3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • apps/web/package.json
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/web/src/utils/supported-time-zones.ts
  • packages/database/package.json
  • packages/database/prisma/migrations/20251222101400_add_api_keys/migration.sql
  • packages/database/prisma/models/user.prisma
  • packages/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.prisma
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • packages/database/prisma/scripts/create-api-key.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/web/package.json
  • apps/web/src/utils/supported-time-zones.ts
  • packages/database/package.json
  • packages/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, run pnpm i18n:scan instead of manually adding keys

Files:

  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • packages/database/prisma/scripts/create-api-key.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/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.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • packages/database/prisma/scripts/create-api-key.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/web/package.json
  • apps/web/src/utils/supported-time-zones.ts
  • packages/database/package.json
**/*.ts

📄 CodeRabbit inference engine (.cursorrules)

On the server use the getTranslations function from @/i18n/server to get the translations

Files:

  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • packages/database/prisma/scripts/create-api-key.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/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.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • packages/database/prisma/scripts/create-api-key.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/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 the useDialog hook from @rallly/ui/dialog for managing dialog state instead of manual useState for 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/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • packages/database/prisma/scripts/create-api-key.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/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.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • apps/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 a username (used for login, normalized) and an optional displayUsername (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
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • apps/web/src/app/api/private/utils/time-slots.ts
  • apps/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-key script follows the existing naming convention and uses the already-available tsx runner.

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 names

CASCADE delete on the foreign key ensures proper cleanup when users are removed.

packages/database/prisma/models/user.prisma (1)

68-68: LGTM!

The ApiKey model is well-structured with:

  • Proper field mappings to snake_case database columns
  • Cascade delete maintaining referential integrity
  • Named indexes matching the migration exactly
  • @db.Text for the hashed key storage

Also 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 createMockApiKey helper 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 in route.ts correctly 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.timingSafeEqual to 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 dates or slots is 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 toTime is after fromTime
  • 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.

@vercel vercel bot temporarily deployed to Preview – landing December 25, 2025 20:01 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 dedupeTimeSlots test suite (lines 53-83), and the implementation calls dedupeTimeSlots directly (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:

  1. Already unit-tested in the dedicated dedupeTimeSlots suite
  2. Inherently verified by the implementation calling dedupeTimeSlots
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between bae6fc3 and 031d6ee.

📒 Files selected for processing (3)
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • apps/web/src/app/api/private/utils/time-slots.test.ts
  • apps/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, run pnpm i18n:scan instead of manually adding keys

Files:

  • apps/web/src/app/api/private/[...route]/route.test.ts
  • apps/web/src/components/forms/poll-details-form.tsx
  • apps/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.ts
  • apps/web/src/components/forms/poll-details-form.tsx
  • apps/web/src/app/api/private/utils/time-slots.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursorrules)

On the server use the getTranslations function from @/i18n/server to get the translations

Files:

  • apps/web/src/app/api/private/[...route]/route.test.ts
  • apps/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.ts
  • apps/web/src/components/forms/poll-details-form.tsx
  • apps/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.ts
  • apps/web/src/components/forms/poll-details-form.tsx
  • apps/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 the useDialog hook from @rallly/ui/dialog for managing dialog state instead of manual useState for 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.ts
  • apps/web/src/components/forms/poll-details-form.tsx
  • apps/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.ts
  • apps/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 a username (used for login, normalized) and an optional displayUsername (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.ts
  • apps/web/src/components/forms/poll-details-form.tsx
  • apps/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 the defaults prop to provide the default text
Always use a composable patterns when building components
Use cn() 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.errors was not being used in the component. Error display is properly handled by the FormMessage component within each FormField.

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.

@vercel vercel bot temporarily deployed to Preview – landing December 25, 2025 20:33 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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:00 or 09: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, and description are 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

📥 Commits

Reviewing files that changed from the base of the PR and between 031d6ee and 6bfccce.

📒 Files selected for processing (5)
  • apps/web/src/app/api/private/[...route]/route.test.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/web/src/app/api/private/schemas.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/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, run pnpm i18n:scan instead of manually adding keys

Files:

  • apps/web/src/app/api/private/schemas.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/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.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/web/src/app/api/private/[...route]/route.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursorrules)

On the server use the getTranslations function from @/i18n/server to get the translations

Files:

  • apps/web/src/app/api/private/schemas.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/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.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/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.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/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 the useDialog hook from @rallly/ui/dialog for managing dialog state instead of manual useState for 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.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/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.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/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 a username (used for login, normalized) and an optional displayUsername (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.ts
  • apps/web/src/app/api/private/utils/api-key.ts
  • apps/web/src/app/api/private/utils/poll.ts
  • apps/web/src/app/api/private/[...route]/route.ts
  • apps/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 verifyApiKey function properly uses crypto.timingSafeEqual to 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 lastUsedAt update via waitUntil avoids 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_secretkey is 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 location in 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 dates or slots must 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 apiError function 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6bfccce and 56216f3.

📒 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, run pnpm i18n:scan instead 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 getTranslations function 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 the useDialog hook from @rallly/ui/dialog for managing dialog state instead of manual useState for 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 a username (used for login, normalized) and an optional displayUsername (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.

Comment on lines +122 to +125
const options = uniqueDates.map((date) => ({
startTime: new Date(`${date}T00:00:00.000Z`),
duration: 0,
}));
Copy link
Contributor

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.

@lukevella lukevella merged commit 13bcadd into main Dec 26, 2025
12 checks passed
@lukevella lukevella deleted the rest-api branch December 26, 2025 09:51
lukevella added a commit that referenced this pull request Dec 27, 2025
@coderabbitai coderabbitai bot mentioned this pull request Dec 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants