Skip to content

Dev - #3

Merged
rekabytes merged 3 commits into
mainfrom
dev
Dec 25, 2025
Merged

Dev#3
rekabytes merged 3 commits into
mainfrom
dev

Conversation

@rekabytes

@rekabytes rekabytes commented Dec 25, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • API key management (generate, revoke, usage) and user dashboard with usage stats
    • Halal Foods API subset with certification and brand data
    • Interactive API documentation with in-browser testing
    • Logto-based authentication and session support
  • Documentation

    • Added linting/formatting guide and updated contributing workflow
    • Prettier/ESLint configuration and ignore rules
  • Chores

    • Env/config updates and Docker port adjustments
    • DB migrations for user tiers, rate limits, and API keys

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

- add dashboard
- add create api key
@coderabbitai

coderabbitai Bot commented Dec 25, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds Logto-based authentication across frontend and backend, implements API key generation/validation and per-user rate limiting, splits food data into natural and halal collections, introduces dashboard and API docs UI, and adds ESLint/Prettier tooling plus related config, migrations, and seeds.

Changes

Cohort / File(s) Summary
Env & Docker
\.env.example`, `packages/kal-frontend/.env.example`, `docker/docker-compose.yml``
Updated Logto endpoints to localhost:3001/3002, added LOGTO_APP_ID, LOGTO_APP_SECRET, SESSION_SECRET, BACKEND_BASE_URL/FRONTEND_URL; docker-compose uses fixed port mappings (3001:3001, 3002:3002).
Formatting & Linting
\.prettierrc`, `.prettierignore`, `eslint.config.mjs`, `docs/linting.md`, `package.json`, `turbo.json``
Added Prettier and ESLint configs, ignore rules, lint/format scripts, and a turbo task for lint:fix; documentation on lint workflow.
Backend: Auth & Middleware
\packages/kal-backend/src/lib/logto.ts`, `packages/kal-backend/src/index.ts`, `packages/kal-backend/src/lib/context.ts`, `packages/kal-backend/src/lib/trpc.ts`, `packages/kal-backend/package.json``
New Logto config/validation, conditional mounting of Logto auth routes, cookie/session middleware, CORS with credentials, and context rewritten to sync users from Logto or headers (upsert).
Backend: API Key & Rate Limit
\packages/kal-backend/src/middleware/api-key-middleware.ts`, `packages/kal-backend/src/middleware/rate-limit.ts``
New API key generation/hashing/validation, middleware to enforce X-API-Key, and MongoDB-backed per-user minute/daily rate-limiting with header helpers.
Backend: Routers & API Surface
\packages/kal-backend/src/routers/api.ts`, `packages/kal-backend/src/routers/food.ts`, `packages/kal-backend/src/routers/halal.ts`, `packages/kal-backend/src/routers/api-keys.ts`, `packages/kal-backend/src/routers/index.ts``
Renamed foodsnatural_foods, added halal router for halal_foods, added apiKeys router (generate/list/revoke/stats), applied API key middleware globally, and exported apiRouter.
Backend: DB & Migrations
\packages/kal-db/migrations/.js`, `packages/kal-db/scripts/seed.ts`, `packages/kal-db/package.json`, `packages/kal-backend/tsconfig.json``
Added migrations for user tier, rate_limit_usage indexes, and api_keys collection; seeds split into natural_foods and halal_foods with indexes; added lint scripts and declarationMap.
Shared Types
\packages/kal-shared/src/types/index.ts`, `packages/kal-shared/package.json``
Added UserTier, rate-limit config/types, LogtoUserInfo, ApiKey types and mapping, plus lint scripts.
Frontend: Logto & Auth Context
\packages/kal-frontend/src/lib/logto.ts`, `packages/kal-frontend/src/lib/auth-context.tsx`, `packages/kal-frontend/src/components/sign-in.tsx`, `packages/kal-frontend/src/components/sign-out.tsx`, `packages/kal-frontend/src/app/callback/route.ts``
New client/server Logto config, AuthProvider/useAuth/AuthUpdater, simple SignIn/SignOut components, and callback route for sign-in handling.
Frontend: Providers, Navbar & Layout
\packages/kal-frontend/src/lib/trpc-provider.tsx`, `packages/kal-frontend/src/components/navbar.tsx`, `packages/kal-frontend/src/app/layout.tsx``
TRPC provider sends auth headers (x-logto-*) and includes credentials; new server-driven Navbar with sign-in/out server actions; layout wrapped with AuthProvider.
Frontend: Dashboard & API Docs
\packages/kal-frontend/src/app/dashboard/.tsx`, `packages/kal-frontend/src/app/api-docs/.tsx`, `packages/kal-frontend/src/app/callback/route.ts`, `packages/kal-frontend/src/app/login-success/*.tsx``
New Dashboard server/page + client UI for key management (generate/revoke, usage), API docs split into server page + interactive client with try-it and curl examples, and login-success flow with countdown.
Frontend: Search & UI Adjustments
\packages/kal-frontend/src/app/search/page.tsx`, `packages/kal-frontend/src/app/page.tsx`, `packages/kal-frontend/src/components/landing/`, `packages/kal-frontend/src/components/navbar.tsx`, `packages/kal-frontend/src/app/globals.css`, `packages/kal-frontend/src/components/`, `packages/kal-frontend/src/lib/trpc.ts`, `packages/kal-frontend/package.json``
Search page now supports dual tabs (natural vs halal) with separate queries and UI; landing import reorderings; large CSS additions for navbar/dashboard/modal styling; minor import tweaks and client package adjustments.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Browser
    participant Frontend
    participant Logto
    participant Backend
    participant Database

    rect rgb(210,230,255)
    Note over User,Logto: Authentication (Sign In) Flow
    User->>Browser: Click "Sign In"
    Browser->>Frontend: Navigate /callback (via Logto redirect)
    Frontend->>Backend: Callback -> handleSignIn (code)
    Backend->>Logto: Exchange code for token / fetch claims
    Logto-->>Backend: Return claims (sub, email, name)
    Backend->>Database: Upsert user by logtoId
    Database-->>Backend: User persisted
    Backend-->>Frontend: Set session cookie
    Frontend->>Browser: Redirect to /login-success
    Browser-->>User: Show countdown then redirect to /dashboard
    end
Loading
sequenceDiagram
    autonumber
    actor User
    participant Browser
    participant Frontend as Dashboard
    participant TRPC
    participant Backend
    participant Database

    rect rgb(220,255,220)
    Note over User,Database: API Key Generation & Revoke
    User->>Browser: Open Generate Key modal
    Browser->>TRPC: Call apiKeys.generate(name, expiration)
    TRPC->>Backend: apiKeysRouter.generate
    Backend->>Backend: Create plain key, hash with SHA-256
    Backend->>Database: Insert api_keys doc (hash, prefix, meta)
    Database-->>Backend: Persisted
    Backend-->>TRPC: Return { plainKey, publicMeta }
    TRPC-->>Browser: Show plainKey once (copy)
    User->>Browser: Click Revoke
    Browser->>TRPC: Call apiKeys.revoke(keyId)
    TRPC->>Backend: Revoke handler
    Backend->>Database: Update isRevoked=true
    Database-->>Backend: Success
    Backend-->>TRPC: Confirm revoke
    TRPC-->>Browser: Refresh list
    end
Loading
sequenceDiagram
    autonumber
    actor User
    participant Browser
    participant Frontend
    participant TRPC
    participant Backend
    participant Database

    rect rgb(255,240,220)
    Note over User,Database: Dual-Tab Food Search (natural vs halal)
    User->>Browser: Select "Halal" tab
    Browser->>TRPC: Call halal.brands() and halal.search(query)
    TRPC->>Backend: Route to halalRouter
    Backend->>Database: Query halal_foods (filters/pagination)
    Database-->>Backend: Results
    Backend-->>TRPC: Mapped DTOs (with brand/cert)
    TRPC-->>Browser: Render halal results
    User->>Browser: Scroll for more
    Browser->>TRPC: halal.allPaginated(nextCursor, brand?)
    TRPC->>Backend: Paginate query
    Backend->>Database: Return next page
    Database-->>Backend: Page results
    Backend-->>TRPC: Return items + nextCursor
    TRPC-->>Browser: Append items
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped through envs and ports with glee,

Keys knitted safe with SHA and a spree,
Natural and Halal lined up in rows,
Dashboards glow where the API grows,
Hooray — the monorepo blooms, carrot tea for me!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.58% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Dev' is too vague and generic, providing no meaningful information about the changeset's primary objective or content. Replace 'Dev' with a descriptive title summarizing the main change, such as 'Add Logto authentication, API key management, and linting setup' or a more specific focus based on the primary objective.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/kal-backend/src/routers/api.ts (1)

124-124: Avoid dynamic import() for ObjectId—use static import.

Dynamic imports add overhead on each request. Since ObjectId is used in multiple routes, import it statically at the top of the file.

🔎 Suggested fix
 import { Router, type Router as RouterType } from "express";
+import { ObjectId } from "mongodb";

 import { getDB } from "../lib/db.js";
 import { validateApiKeyMiddleware } from "../middleware/api-key-middleware.js";

 // Then in the route handlers, remove:
-    const { ObjectId } = await import("mongodb");

This applies to both lines 124 and 331.

🧹 Nitpick comments (35)
packages/kal-frontend/src/app/search/page.tsx (5)

45-50: Consider resetting search state when switching tabs.

When the user switches tabs, inputValue and searchQuery are not reset. This means a search query from the "natural" tab will persist and immediately apply to the "halal" tab (and vice versa), which may yield unexpected results or no results at all.

🔎 Proposed fix
  // Reset filters when switching tabs
  useEffect(() => {
    setSelectedCategory("");
    setSelectedBrand("");
    setSelectedFood(null);
+   setInputValue("");
+   setSearchQuery("");
  }, [activeTab]);

196-218: Consider adding accessibility attributes to tab buttons.

The tab buttons function as toggles but lack aria-pressed or a proper tab role to indicate the selected state for screen reader users.

🔎 Proposed fix
          <button
            onClick={() => setActiveTab("natural")}
+           aria-pressed={activeTab === "natural"}
            className={`px-6 py-3 rounded-xl font-medium transition-all duration-200
              ${activeTab === "natural"
                ? "bg-accent text-dark shadow-lg shadow-accent/20"
                : "bg-dark-surface text-content-secondary border border-dark-border hover:border-accent/30"
              }`}
          >
            🍚 Natural Foods
          </button>
          <button
            onClick={() => setActiveTab("halal")}
+           aria-pressed={activeTab === "halal"}
            className={`px-6 py-3 rounded-xl font-medium transition-all duration-200
              ${activeTab === "halal"
                ? "bg-emerald-500 text-white shadow-lg shadow-emerald-500/20"
                : "bg-dark-surface text-content-secondary border border-dark-border hover:border-emerald-500/30"
              }`}
          >
            Halal Certified
          </button>

360-414: Consider using a proper type guard instead of property checks.

The "brand" in food check is used as a discriminator, but this is fragile—if a Food item ever includes a brand property, the logic will break. Additionally, the repeated (food as HalalFood) casts are verbose.

A type guard function would improve both type safety and readability:

🔎 Proposed refactor

Add a type guard function at the top of the component or in a utils file:

function isHalalFood(food: Food | HalalFood): food is HalalFood {
  return "halalCertifier" in food || "halalCertYear" in food;
}

Then use it in the render logic:

-              {foods.map((food) => {
-                const isHalalFood = "brand" in food;
+              {foods.map((food) => {
+                const halalFood = isHalalFood(food) ? food : null;
                 return (
                   <button
                     ...
-                          {isHalalFood && (food as HalalFood).brand && (
-                            <span className="...">
-                              {(food as HalalFood).brand}
+                          {halalFood?.brand && (
+                            <span className="...">
+                              {halalFood.brand}
                             </span>
                           )}

495-512: Minor indentation inconsistency in modal.

Line 507 has inconsistent indentation compared to the surrounding JSX. This doesn't affect functionality but makes the code structure harder to follow.

🔎 Proposed fix
                   {selectedFood.category && (
                     <span className="text-xs text-content-muted">
                       {selectedFood.category}
                     </span>
                   )}
                 </div>
-                    {"brand" in selectedFood && (selectedFood as HalalFood).halalCertifier && (
-                    <p className="text-xs text-emerald-400 mt-2">
-                      {(selectedFood as HalalFood).halalCertifier} Certified
-                      {(selectedFood as HalalFood).halalCertYear && ` (${(selectedFood as HalalFood).halalCertYear})`}
-                    </p>
+                {"brand" in selectedFood && (selectedFood as HalalFood).halalCertifier && (
+                  <p className="text-xs text-emerald-400 mt-2">
+                    {(selectedFood as HalalFood).halalCertifier} Certified
+                    {(selectedFood as HalalFood).halalCertYear && ` (${(selectedFood as HalalFood).halalCertYear})`}
+                  </p>
                 )}

484-520: Consider adding keyboard support to close the modal.

The modal can only be closed by clicking the close button or selecting a different food item. Adding support for the Escape key would improve keyboard accessibility.

🔎 Proposed enhancement

Add a useEffect to handle the Escape key:

useEffect(() => {
  const handleKeyDown = (e: KeyboardEvent) => {
    if (e.key === "Escape" && selectedFood) {
      setSelectedFood(null);
    }
  };
  
  document.addEventListener("keydown", handleKeyDown);
  return () => document.removeEventListener("keydown", handleKeyDown);
}, [selectedFood]);
packages/kal-frontend/src/components/sign-in.tsx (1)

1-16: Add error handling and loading state to the SignIn component.

The component is actively used in the navbar with server actions. Adding error handling and loading state would improve the user experience by providing feedback during the sign-in process and gracefully handling any failures.

🔎 Suggested improvement with error handling and loading state
 'use client';
+
+import { useState } from 'react';

 type Props = {
   onSignIn: () => Promise<void>;
 };

 export default function SignIn({ onSignIn }: Props) {
+  const [loading, setLoading] = useState(false);
+
+  const handleSignIn = async () => {
+    try {
+      setLoading(true);
+      await onSignIn();
+    } catch (error) {
+      console.error('Sign in failed:', error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
   return (
     <button
-      onClick={() => onSignIn()}
+      onClick={handleSignIn}
+      disabled={loading}
       className="sign-in-btn"
     >
-      Sign In
+      {loading ? 'Signing in...' : 'Sign In'}
     </button>
   );
 }
packages/kal-frontend/src/app/api-docs/client.tsx (2)

158-174: Shared response state may cause UX confusion.

The response and error state is shared across all endpoints. When a user tries one endpoint, then expands another, the previous response remains visible. Consider keying the response by activeEndpoint or clearing it when a different endpoint is expanded.

🔎 Proposed approach
   const tryEndpoint = async (example: string) => {
     setLoading(true);
     setError(null);
     setResponse(null);
+    // Store which endpoint this response is for
+    const currentEndpoint = activeEndpoint;

     try {
       const apiUrl = getApiUrl();
       const fullUrl = apiUrl ? `${apiUrl}${example}` : example;
       const res = await fetch(fullUrl);
       const data = await res.json();
-      setResponse(JSON.stringify(data, null, 2));
+      // Only update if still viewing the same endpoint
+      if (currentEndpoint === activeEndpoint) {
+        setResponse(JSON.stringify(data, null, 2));
+      }
     } catch (err) {
-      setError(err instanceof Error ? err.message : "Failed to fetch");
+      if (currentEndpoint === activeEndpoint) {
+        setError(err instanceof Error ? err.message : "Failed to fetch");
+      }
     } finally {
       setLoading(false);
     }
   };

Alternatively, clear response when switching endpoints:

onClick={() => {
  setActiveEndpoint(activeEndpoint === endpoint.id ? null : endpoint.id);
  setResponse(null);
  setError(null);
}}

274-309: Consider moving CurlExampleCard outside the component.

CurlExampleCard is defined inside APIDocsClient, so it's recreated on every render. Since it only depends on props and the onSignIn callback, extracting it as a separate component or memoizing it would avoid unnecessary re-renders.

packages/kal-backend/src/lib/db.ts (1)

10-18: Consider validating required MongoDB environment variables.

If MONGODB_USER, MONGODB_PASSWORD, or MONGODB_DATABASE are undefined when DATABASE_URL is not set, the constructed URI will contain literal "undefined" strings, leading to connection failures with unclear error messages.

🔎 Suggested validation
 const getDatabaseUri = () => {
   if (process.env.DATABASE_URL) {
     return process.env.DATABASE_URL;
   }
   
   const {
     MONGODB_HOST = "localhost",
     MONGODB_PORT = "27017",
     MONGODB_USER,
     MONGODB_PASSWORD,
     MONGODB_DATABASE,
   } = process.env;
+
+  if (!MONGODB_USER || !MONGODB_PASSWORD || !MONGODB_DATABASE) {
+    throw new Error(
+      "Missing required MongoDB env vars: MONGODB_USER, MONGODB_PASSWORD, MONGODB_DATABASE. " +
+      "Alternatively, provide DATABASE_URL."
+    );
+  }
   
   return `mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DATABASE}?authSource=admin`;
 };
packages/kal-frontend/src/app/callback/route.ts (1)

7-12: Consider adding error handling for sign-in failures.

If handleSignIn throws (e.g., invalid state, CSRF mismatch, network error), the user will see an unhandled error page. Consider wrapping in try-catch to redirect to an error page or show a user-friendly message.

🔎 Proposed error handling
 export async function GET(request: NextRequest) {
   const searchParams = request.nextUrl.searchParams;
-  await handleSignIn(logtoConfig, searchParams);
 
-  // Redirect to login success page, which will then redirect to dashboard
-  redirect("/login-success");
+  try {
+    await handleSignIn(logtoConfig, searchParams);
+    // Redirect to login success page, which will then redirect to dashboard
+    redirect("/login-success");
+  } catch (error) {
+    console.error("Sign-in callback failed:", error);
+    // Redirect to home or error page with error indication
+    redirect("/?error=signin_failed");
+  }
 }
docker/docker-compose.yml (1)

40-41: Consider the operational impact of hardcoded ports.

Changing from variable port mappings (${LOGTO_PORT:-3301}:3001) to fixed ports (3001:3001) simplifies the configuration but removes deployment flexibility. This means:

  • Multiple instances cannot run on the same host without manual port overrides
  • Port conflicts will require docker-compose.override.yml or command-line flags

If this setup is only for local development, the simplification is reasonable. For production or multi-environment deployments, consider restoring the environment variable approach.

packages/kal-frontend/src/components/sign-out.tsx (1)

7-16: Consider adding error handling and loading state.

The onSignOut handler is asynchronous but lacks error handling and loading feedback. Users won't see any indication that sign-out is in progress if the operation is slow or fails.

🔎 Suggested enhancement with loading state
 'use client';
 
+import { useState } from 'react';
+
 type Props = {
   onSignOut: () => Promise<void>;
 };
 
 export default function SignOut({ onSignOut }: Props) {
+  const [isLoading, setIsLoading] = useState(false);
+
+  const handleSignOut = async () => {
+    setIsLoading(true);
+    try {
+      await onSignOut();
+    } catch (error) {
+      console.error('Sign out failed:', error);
+      // Optionally show error toast/message
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
   return (
     <button
-      onClick={() => onSignOut()}
+      onClick={handleSignOut}
+      disabled={isLoading}
       className="sign-out-btn"
     >
-      Sign Out
+      {isLoading ? 'Signing out...' : 'Sign Out'}
     </button>
   );
 }
packages/kal-frontend/src/app/login-success/page.tsx (1)

13-26: Consider a more generic fallback username.

The fallback chain claims?.name || claims?.email || "Developer" is functional, but the final fallback "Developer" might not accurately represent all users. Consider using a more neutral term like "User" or "Member".

🔎 Suggested change
     <LoginSuccessClient
-      userName={claims?.name || claims?.email || "Developer"}
+      userName={claims?.name || claims?.email || "User"}
     />
packages/kal-db/migrations/20241225000003_create_api_keys.js (1)

20-24: Clarify the comment about hashed keys.

The comment states "key is hashed" but the index is on the plain key field. If the application stores hashed API keys (which is good security practice), the comment is accurate. If keys are stored in plaintext, consider updating the comment or implementing key hashing.

packages/kal-db/scripts/seed.ts (3)

189-201: Consider dropping the old foods collection entirely.

The old foods collection is cleared but not dropped, which may cause confusion if developers query it expecting data. If it's being deprecated in favor of natural_foods and halal_foods, consider dropping it.

🔎 Proposed change
-    // Clear old 'foods' collection (if exists)
-    await oldFoodsCollection.deleteMany({});
-    console.log("🗑️  Cleared old 'foods' collection");
+    // Drop old 'foods' collection (if exists)
+    try {
+      await db.dropCollection("foods");
+      console.log("🗑️  Dropped old 'foods' collection");
+    } catch (e) {
+      console.log("ℹ️  'foods' collection doesn't exist, skipping drop");
+    }

206-214: Index creation may fail on re-runs if index options differ.

createIndex will throw if an index with the same name but different options exists. Consider using { background: true } or wrapping in try-catch for idempotent re-runs.

🔎 Proposed fix for idempotent index creation
     // Create indexes for natural_foods
-    await naturalCollection.createIndex({ name: "text" });
-    await naturalCollection.createIndex({ category: 1 });
+    await naturalCollection.dropIndexes().catch(() => {});
+    await naturalCollection.createIndex({ name: "text" });
+    await naturalCollection.createIndex({ category: 1 });
     console.log("✅ Created indexes on 'natural_foods' collection");

Alternatively, wrap each createIndex in a try-catch that ignores "index already exists" errors.


32-154: Large inline data arrays reduce maintainability.

Consider extracting the seed data to separate JSON files (e.g., data/natural-foods.json, data/halal-foods.json) for easier maintenance and cleaner separation of code and data.

packages/kal-frontend/src/app/api-docs/page.tsx (2)

39-43: Security-by-obscurity is ineffective here.

The comment claims this prevents viewing data via DevTools, but:

  1. The API endpoints are publicly documented and discoverable
  2. The curl commands contain no secrets—just endpoint URLs
  3. The structure (comment, type) is still sent to the client

If the goal is to gate documentation access, consider redirecting unauthenticated users entirely or removing this logic since it adds complexity without meaningful protection.


50-57: Inline server actions in JSX props may cause issues.

Defining server actions inline within props with 'use server' is unconventional. Consider extracting these to named functions at module scope for clarity and to ensure proper serialization.

🔎 Suggested refactor
+async function handleSignIn() {
+  'use server';
+  await signIn(logtoConfig);
+}
+
+async function handleSignOut() {
+  'use server';
+  await signOut(logtoConfig);
+}
+
 export default async function APIDocsPage() {
   const { isAuthenticated, claims } = await getLogtoContext(logtoConfig);
   // ...
   return (
     <APIDocsClient 
       isAuthenticated={isAuthenticated}
       userEmail={claims?.email || claims?.sub}
       curlExamples={safeCurlExamples}
-      onSignIn={async () => {
-        'use server';
-        await signIn(logtoConfig);
-      }}
-      onSignOut={async () => {
-        'use server';
-        await signOut(logtoConfig);
-      }}
+      onSignIn={handleSignIn}
+      onSignOut={handleSignOut}
     />
   );
 }
packages/kal-frontend/src/app/dashboard/page.tsx (1)

31-33: Consider using Next.js Link for client-side navigation.

Using a plain <a> tag will trigger a full page reload. For better UX and performance in Next.js, use the Link component.

🔎 Suggested change
+import Link from "next/link";
+
 // In the JSX:
-          <a href="/" className="back-link">
+          <Link href="/" className="back-link">
             ← Back to Home
-          </a>
+          </Link>
packages/kal-db/scripts/seed-safe.ts (2)

195-211: Indexes may not be created if seed is interrupted.

If the script fails after insertMany but before createIndex, subsequent runs will skip the entire block because the collection is non-empty. Consider separating index creation from data seeding, or using createIndex with { background: true } idempotently outside the empty-check.

🔎 Suggested approach
+    // Always ensure indexes exist (createIndex is idempotent)
+    await naturalCollection.createIndex({ name: "text" });
+    await naturalCollection.createIndex({ category: 1 });
+
     if (naturalCount > 0) {
       console.log(`ℹ️  natural_foods collection already has ${naturalCount} documents - skipping`);
     } else {
       await naturalCollection.insertMany(naturalFoods);
       console.log(`✅ Inserted ${naturalFoods.length} items into 'natural_foods' collection`);
-      
-      // Create indexes
-      await naturalCollection.createIndex({ name: "text" });
-      await naturalCollection.createIndex({ category: 1 });
-      console.log("✅ Created indexes on 'natural_foods' collection");
     }
+    console.log("✅ Ensured indexes on 'natural_foods' collection");

165-180: Limited halal foods dataset.

The halalFoods array contains only 13 Ramly products. Consider documenting that this is intentional seed data or expanding with additional brands/items for a more representative dataset.

packages/kal-backend/src/routers/api.ts (1)

38-49: Consider extracting food-to-DTO mapping helpers to reduce duplication.

The mapping logic for natural foods and halal foods is repeated across multiple endpoints. Extract reusable mapper functions.

🔎 Example helper
const mapNaturalFood = (food: Document) => ({
  id: food._id.toString(),
  name: food.name,
  calories: food.calories,
  protein: food.protein ?? 0,
  carbs: food.carbs ?? 0,
  fat: food.fat ?? 0,
  serving: food.serving,
  category: food.category,
});

const mapHalalFood = (food: Document) => ({
  ...mapNaturalFood(food),
  brand: food.brand,
  halalCertifier: food.halalCertifier,
  halalCertYear: food.halalCertYear,
});

Also applies to: 216-228, 273-285, 352-364

packages/kal-backend/src/middleware/rate-limit.ts (1)

43-43: Avoid setting _id to an empty string.

Setting _id: "" as any is unnecessary—MongoDB will auto-generate the _id if omitted. This also causes TypeScript to lose type safety.

-    usage = {
-      _id: "" as any,
-      userId,
+    const newUsage = {
+      userId,
       // ... rest of fields
     };
+    await collection.insertOne(newUsage);
packages/kal-backend/src/routers/halal.ts (1)

31-47: Unbounded query may cause performance issues at scale.

The all procedure fetches all documents without pagination. If halal_foods grows large, this will consume significant memory and increase response times. Consider adding a reasonable limit or deprecating in favor of allPaginated.

🔎 Suggested mitigation
   // Get all halal foods (public)
-  all: publicProcedure.query(async ({ ctx }) => {
-    const foods = await ctx.db.collection("halal_foods").find({}).toArray();
+  all: publicProcedure.query(async ({ ctx }) => {
+    const foods = await ctx.db
+      .collection("halal_foods")
+      .find({})
+      .limit(500) // Safeguard against unbounded results
+      .toArray();
packages/kal-frontend/src/app/dashboard/client.tsx (2)

91-97: Add error handling for clipboard API.

navigator.clipboard.writeText() can fail (e.g., non-HTTPS context, permissions denied). Consider wrapping in try-catch with user feedback.

🔎 Suggested fix
   const handleCopyKey = () => {
     if (generatedKey) {
-      navigator.clipboard.writeText(generatedKey);
-      setCopied(true);
-      setTimeout(() => setCopied(false), 2000);
+      navigator.clipboard.writeText(generatedKey)
+        .then(() => {
+          setCopied(true);
+          setTimeout(() => setCopied(false), 2000);
+        })
+        .catch(() => {
+          // Fallback or show error message
+          alert("Failed to copy. Please copy manually.");
+        });
     }
   };

107-111: Consider replacing confirm() with a custom modal.

The native confirm() dialog blocks the main thread and has inconsistent styling across browsers. A custom confirmation modal would provide better UX and match the existing modal pattern used for key generation.

packages/kal-backend/src/index.ts (1)

109-117: Session cookie missing secure and sameSite options.

For production with credentials-enabled CORS, consider setting cookie.secure: true (requires HTTPS) and cookie.sameSite: 'lax' or 'none' to prevent CSRF and ensure cross-origin requests work properly.

🔎 Proposed enhancement
   app.use(
     session({
       secret: process.env.SESSION_SECRET || "dev-secret-change-in-production",
-      cookie: { maxAge: 14 * 24 * 60 * 60 * 1000 }, // 14 days
+      cookie: { 
+        maxAge: 14 * 24 * 60 * 60 * 1000, // 14 days
+        secure: process.env.NODE_ENV === "production",
+        sameSite: "lax",
+      },
       resave: false,
       saveUninitialized: false,
     })
   );
packages/kal-backend/src/middleware/api-key-middleware.ts (2)

54-58: Suspicious type casting for ObjectId lookup.

The cast new ObjectId(keyDoc.userId) as unknown as string suggests a type mismatch between the User interface (where _id is typed as string) and MongoDB's actual ObjectId. This works at runtime but obscures the real types. Consider aligning the User type with MongoDB's ObjectId or using a wrapper type.

🔎 Proposed fix
   // Get the user
   const { ObjectId } = await import("mongodb");
-  const user = await db.collection<User>("users").findOne({
-    _id: new ObjectId(keyDoc.userId) as unknown as string,
-  });
+  const user = await db.collection<User>("users").findOne({
+    _id: new ObjectId(keyDoc.userId),
+  } as any);

Alternatively, update the shared User type to use ObjectId | string for _id, or create a DB-specific user type.


64-68: Fire-and-forget update is acceptable but consider logging context.

The lastUsedAt update is correctly non-blocking. The .catch(console.error) will log errors, but consider adding context (e.g., key ID) for debugging.

packages/kal-backend/src/routers/api-keys.ts (2)

46-53: getMe returns fallback values that may mask issues.

Returning empty strings and "free" tier when ctx.user is undefined could hide authentication problems. Since this uses protectedProcedure, the user should always exist.

🔎 Consider throwing if user is missing
   getMe: protectedProcedure.query(async ({ ctx }) => {
+    if (!ctx.user) {
+      throw new TRPCError({ code: "UNAUTHORIZED", message: "User not found" });
+    }
     return {
-      _id: ctx.user?._id?.toString() || "",
-      name: ctx.user?.name || "",
-      email: ctx.user?.email || "",
-      tier: ctx.user?.tier || "free",
+      _id: ctx.user._id.toString(),
+      name: ctx.user.name || "",
+      email: ctx.user.email || "",
+      tier: ctx.user.tier,
     };
   }),

124-146: Use TRPCError for consistency with tRPC error handling.

Throwing a plain Error doesn't integrate well with tRPC's error handling. Use TRPCError with an appropriate code.

🔎 Proposed fix
+import { TRPCError } from "@trpc/server";
+
 // In the revoke mutation:
       if (result.matchedCount === 0) {
-        throw new Error("API key not found or already revoked");
+        throw new TRPCError({
+          code: "NOT_FOUND",
+          message: "API key not found or already revoked",
+        });
       }
packages/kal-backend/src/lib/context.ts (3)

52-61: Same ObjectId type casting pattern as elsewhere.

Consider creating a shared helper for typed ObjectId lookups to avoid this pattern throughout the codebase.


93-101: Redundant condition on line 95.

The check if (headerLogtoId) at line 95 is always true since it's inside the if (headerLogtoId) block at line 86.

🔎 Proposed simplification
       // If user not found but we have claims in headers (trusted from frontend), create/sync them
       if (!user) {
-        // Proceed if we at least have an email or if the ID is sufficient for a basic record
-        if (headerLogtoId) {
-          user = await syncUserFromLogto(db, {
-            sub: headerLogtoId,
-            email: headerEmail,
-            name: headerName,
-          });
-        }
+        user = await syncUserFromLogto(db, {
+          sub: headerLogtoId,
+          email: headerEmail,
+          name: headerName,
+        });
       } else if (headerName && !user.name) {

102-110: Partial state update doesn't reflect all DB changes locally.

After updating the user in the database, the local user object is manually patched. If additional fields were updated (e.g., updatedAt), they won't be reflected. Consider re-fetching or ensuring consistency.

🔎 Alternative: re-fetch after update
       } else if (headerName && !user.name) {
-         // Update if local name is missing but header has one
-         await db.collection<User>("users").updateOne(
-           { _id: user._id }, 
-           { $set: { name: headerName, email: headerEmail || user.email } } 
-         );
-         user.name = headerName;
-         if (headerEmail) user.email = headerEmail;
+         // Update if local name is missing but header has one
+         const updatedUser = await db.collection<User>("users").findOneAndUpdate(
+           { _id: user._id }, 
+           { $set: { name: headerName, email: headerEmail || user.email, updatedAt: new Date() } },
+           { returnDocument: "after" }
+         );
+         if (updatedUser) user = updatedUser;
       }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb63501 and 3f302bf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (56)
  • .env.example
  • .prettierignore
  • .prettierrc
  • README.md
  • docker/docker-compose.yml
  • docs/linting.md
  • eslint.config.mjs
  • package.json
  • packages/kal-backend/package.json
  • packages/kal-backend/src/index.ts
  • packages/kal-backend/src/lib/context.ts
  • packages/kal-backend/src/lib/db.ts
  • packages/kal-backend/src/lib/logto.ts
  • packages/kal-backend/src/lib/trpc.ts
  • packages/kal-backend/src/middleware/api-key-middleware.ts
  • packages/kal-backend/src/middleware/rate-limit.ts
  • packages/kal-backend/src/routers/api-keys.ts
  • packages/kal-backend/src/routers/api.ts
  • packages/kal-backend/src/routers/food.ts
  • packages/kal-backend/src/routers/halal.ts
  • packages/kal-backend/src/routers/index.ts
  • packages/kal-backend/tsconfig.json
  • packages/kal-db/migrations/20241225000001_add_user_tiers.js
  • packages/kal-db/migrations/20241225000002_rate_limit_indexes.js
  • packages/kal-db/migrations/20241225000003_create_api_keys.js
  • packages/kal-db/package.json
  • packages/kal-db/scripts/seed-safe.ts
  • packages/kal-db/scripts/seed.ts
  • packages/kal-frontend/.env.example
  • packages/kal-frontend/package.json
  • packages/kal-frontend/src/app/api-docs/client.tsx
  • packages/kal-frontend/src/app/api-docs/page.tsx
  • packages/kal-frontend/src/app/callback/route.ts
  • packages/kal-frontend/src/app/dashboard/client.tsx
  • packages/kal-frontend/src/app/dashboard/page.tsx
  • packages/kal-frontend/src/app/globals.css
  • packages/kal-frontend/src/app/layout.tsx
  • packages/kal-frontend/src/app/login-success/client.tsx
  • packages/kal-frontend/src/app/login-success/page.tsx
  • packages/kal-frontend/src/app/page.tsx
  • packages/kal-frontend/src/app/search/page.tsx
  • packages/kal-frontend/src/components/landing/CTA.tsx
  • packages/kal-frontend/src/components/landing/FAQ.tsx
  • packages/kal-frontend/src/components/landing/Footer.tsx
  • packages/kal-frontend/src/components/landing/Navbar.tsx
  • packages/kal-frontend/src/components/landing/SampleFoods.tsx
  • packages/kal-frontend/src/components/navbar.tsx
  • packages/kal-frontend/src/components/sign-in.tsx
  • packages/kal-frontend/src/components/sign-out.tsx
  • packages/kal-frontend/src/lib/auth-context.tsx
  • packages/kal-frontend/src/lib/logto.ts
  • packages/kal-frontend/src/lib/trpc-provider.tsx
  • packages/kal-frontend/src/lib/trpc.ts
  • packages/kal-shared/package.json
  • packages/kal-shared/src/types/index.ts
  • turbo.json
🧰 Additional context used
🧬 Code graph analysis (12)
packages/kal-backend/src/lib/logto.ts (1)
packages/kal-frontend/src/lib/logto.ts (1)
  • logtoConfig (3-10)
packages/kal-frontend/src/app/login-success/page.tsx (3)
packages/kal-frontend/src/app/dashboard/page.tsx (1)
  • metadata (8-11)
packages/kal-frontend/src/app/layout.tsx (1)
  • metadata (10-13)
packages/kal-frontend/src/app/login-success/client.tsx (1)
  • LoginSuccessClient (10-48)
packages/kal-backend/src/routers/index.ts (3)
packages/kal-backend/src/lib/trpc.ts (1)
  • router (7-7)
packages/kal-backend/src/routers/halal.ts (1)
  • halalRouter (5-135)
packages/kal-backend/src/routers/api-keys.ts (1)
  • apiKeysRouter (42-179)
packages/kal-frontend/src/lib/auth-context.tsx (1)
packages/kal-backend/src/lib/context.ts (1)
  • createContext (63-125)
packages/kal-frontend/src/app/dashboard/client.tsx (3)
packages/kal-shared/src/types/index.ts (1)
  • ApiKeyExpiration (53-53)
packages/kal-frontend/src/lib/auth-context.tsx (2)
  • AuthUpdater (50-68)
  • useAuth (19-21)
packages/kal-frontend/src/lib/trpc.ts (1)
  • trpc (5-5)
packages/kal-backend/src/middleware/api-key-middleware.ts (4)
packages/kal-shared/src/types/index.ts (2)
  • User (6-14)
  • ApiKey (55-67)
packages/kal-backend/src/lib/db.ts (1)
  • getDB (35-38)
packages/kal-db/migrations/20241225000001_add_user_tiers.js (1)
  • result (9-12)
packages/kal-backend/src/middleware/rate-limit.ts (2)
  • checkRateLimit (18-110)
  • getRateLimitHeaders (125-150)
packages/kal-backend/src/routers/api-keys.ts (4)
packages/kal-shared/src/types/index.ts (4)
  • ApiKeyExpiration (53-53)
  • ApiKey (55-67)
  • ApiKeyPublic (70-79)
  • RateLimitUsage (30-38)
packages/kal-backend/src/lib/trpc.ts (2)
  • router (7-7)
  • protectedProcedure (26-26)
packages/kal-backend/src/middleware/api-key-middleware.ts (2)
  • generateApiKey (21-28)
  • hashApiKey (14-16)
packages/kal-db/migrations/20241225000001_add_user_tiers.js (1)
  • result (9-12)
packages/kal-frontend/src/lib/logto.ts (1)
packages/kal-backend/src/lib/logto.ts (1)
  • logtoConfig (4-9)
packages/kal-frontend/src/app/search/page.tsx (1)
packages/kal-frontend/src/lib/trpc.ts (1)
  • trpc (5-5)
packages/kal-backend/src/routers/api.ts (2)
packages/kal-backend/src/middleware/api-key-middleware.ts (1)
  • validateApiKeyMiddleware (76-146)
packages/kal-backend/src/lib/db.ts (1)
  • getDB (35-38)
packages/kal-frontend/src/components/navbar.tsx (4)
packages/kal-frontend/src/components/landing/Navbar.tsx (1)
  • Navbar (16-87)
packages/kal-frontend/src/lib/logto.ts (1)
  • logtoConfig (3-10)
packages/kal-frontend/src/components/sign-out.tsx (1)
  • SignOut (7-16)
packages/kal-frontend/src/components/sign-in.tsx (1)
  • SignIn (7-16)
packages/kal-frontend/src/lib/trpc-provider.tsx (2)
packages/kal-frontend/src/lib/auth-context.tsx (1)
  • useAuth (19-21)
packages/kal-frontend/src/lib/trpc.ts (1)
  • trpc (5-5)
🪛 Biome (2.1.2)
packages/kal-frontend/src/app/api-docs/client.tsx

[error] 422-423: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

🪛 dotenv-linter (4.0.0)
packages/kal-frontend/.env.example

[warning] 6-6: [UnorderedKey] The NEXT_PUBLIC_LOGTO_APP_ID key should go before the NEXT_PUBLIC_LOGTO_ENDPOINT key

(UnorderedKey)


[warning] 7-7: [UnorderedKey] The NEXT_PUBLIC_APP_URL key should go before the NEXT_PUBLIC_LOGTO_APP_ID key

(UnorderedKey)

.env.example

[warning] 14-14: [UnorderedKey] The LOGTO_ADMIN_ENDPOINT key should go before the LOGTO_ENDPOINT key

(UnorderedKey)


[warning] 30-30: [UnorderedKey] The BACKEND_BASE_URL key should go before the BACKEND_PORT key

(UnorderedKey)


[warning] 31-31: [UnorderedKey] The FRONTEND_PORT key should go before the NEXT_PUBLIC_API_URL key

(UnorderedKey)


[warning] 32-32: [UnorderedKey] The FRONTEND_URL key should go before the NEXT_PUBLIC_API_URL key

(UnorderedKey)

🔇 Additional comments (63)
packages/kal-frontend/src/components/landing/FAQ.tsx (1)

3-6: Good import organization.

The blank line at line 4 properly separates React imports from local component imports, following standard conventions. This aligns with ESLint/Prettier formatting rules mentioned in the PR and improves code readability.

turbo.json (1)

15-18: LGTM!

The lint:fix task is correctly configured with cache: false since it modifies source files, and the ^lint:fix dependency ensures workspace dependencies are processed first.

packages/kal-frontend/src/app/search/page.tsx (5)

19-26: LGTM!

The HalalFood interface correctly extends Food with halal-specific optional fields, and the TabType union type provides good type safety for tab switching.


62-88: LGTM!

The natural foods queries are properly gated by activeTab === "natural", preventing unnecessary network requests when the halal tab is active. The infinite query configuration with getNextPageParam and initialCursor looks correct.


90-116: LGTM!

The halal foods queries mirror the natural foods query structure consistently, with proper enabled guards based on activeTab.


118-146: LGTM!

The Intersection Observer is correctly configured with appropriate guards for the active tab and search mode. The dependency array is comprehensive, ensuring the observer is recreated when relevant state changes.


148-171: LGTM!

The data derivation logic correctly selects the appropriate data source based on the active tab and search mode. The ternary expressions are clear and the types align properly.

packages/kal-frontend/src/components/landing/CTA.tsx (1)

2-2: LGTM! Import ordering aligns with new Prettier config.

The import reordering is a formatting change that aligns with the new ESLint/Prettier configuration introduced in this PR.

packages/kal-frontend/src/components/landing/Footer.tsx (1)

2-2: LGTM! Formatting aligns with new Prettier standards.

The blank line addition follows standard import grouping conventions from the new Prettier configuration.

packages/kal-frontend/src/components/landing/SampleFoods.tsx (1)

1-1: LGTM! Import ordering is now consistent.

The Button import repositioning aligns with the standardized import ordering from the new ESLint/Prettier setup.

packages/kal-backend/tsconfig.json (1)

9-9: LGTM! Declaration maps improve debugging.

Enabling declarationMap generates .d.ts.map files that enhance IDE navigation and debugging support for TypeScript declarations across the monorepo.

.prettierignore (1)

1-20: LGTM! Standard ignore patterns for Prettier.

The ignore patterns appropriately exclude dependencies, build artifacts, lock files, and generated code from formatting. This is standard practice and well-suited for the monorepo structure.

packages/kal-shared/package.json (1)

10-11: LGTM! Lint scripts align with monorepo standards.

The addition of lint and lint:fix scripts integrates kal-shared with the broader linting infrastructure established in this PR.

.prettierrc (1)

1-12: LGTM! Balanced and standard Prettier configuration.

The configuration uses sensible defaults that promote consistency across the monorepo. Settings like printWidth: 80, trailingComma: "es5", and endOfLine: "lf" are well-suited for team collaboration and cross-platform development.

README.md (1)

215-221: LGTM! Clear and actionable contributing workflow.

The updated workflow emphasizes linting and provides clear, sequential steps for contributors. The reference to docs/linting.md offers additional guidance without cluttering the main README.

packages/kal-db/package.json (1)

13-14: LGTM!

The lint scripts are properly configured and align with the monorepo's linting strategy.

docs/linting.md (1)

1-81: LGTM!

Comprehensive linting documentation that clearly explains the tooling setup, workflow, and VS Code integration. The pre-push checklist is particularly helpful.

packages/kal-shared/src/types/index.ts (4)

1-14: LGTM!

The user tier system and integration into the User interface is well-designed.


16-38: LGTM!

The rate limiting types and configuration are well-structured. The hardcoded limits in RATE_LIMITS establish clear tier boundaries: free (100/day, 10/min), tier_1 (250/day, 30/min), and tier_2 (700/day, 50/min).


40-48: LGTM!

The LogtoUserInfo interface correctly models the JWT claims from Logto's authentication response.


50-79: LGTM!

The API key types follow security best practices by storing hashed keys and only exposing the prefix for display. The separation between ApiKey and ApiKeyPublic prevents accidental exposure of sensitive fields.

packages/kal-frontend/package.json (2)

9-11: LGTM!

The lint scripts are properly configured and align with the monorepo's linting strategy.


14-21: LGTM!

The addition of @logto/next for authentication and kal-shared for shared types are appropriate dependencies for the frontend.

packages/kal-frontend/src/app/globals.css (1)

45-665: LGTM!

Comprehensive styling system with good practices:

  • Consistent use of CSS custom properties for theming
  • Accessibility features (focus-visible, selection colors)
  • Responsive design with mobile breakpoints
  • Clear component-scoped class naming
  • Smooth transitions and animations
package.json (1)

9-11: LGTM!

The lint and format scripts follow standard patterns and integrate well with Turbo's task orchestration.

eslint.config.mjs (3)

1-32: Well-structured ESLint flat config for monorepo.

The configuration is well-organized with clear separation of concerns for backend/shared TypeScript, React/Next.js frontend, and plain JavaScript files. The ignore patterns and rule selections follow best practices.


56-94: TypeScript rules are appropriately configured.

Good use of ignore patterns for unused variables (^_ prefix) and warning-level severity for no-explicit-any and consistent-type-imports. The import ordering rules will help maintain consistent code organization.


148-154: React hooks rules correctly configured.

rules-of-hooks as error and exhaustive-deps as warn is the recommended configuration for catching hook violations while allowing flexibility for intentional dependency omissions.

packages/kal-frontend/src/app/api-docs/client.tsx (2)

10-16: getApiUrl correctly handles client-side environment.

The function properly checks for browser environment before accessing process.env.NEXT_PUBLIC_API_URL and falls back gracefully to empty string for relative paths.


315-571: Well-structured API documentation UI.

The component provides a clean, organized presentation of API endpoints with interactive "Try it" functionality. The authentication-gated cURL examples and responsive design are well implemented.

packages/kal-frontend/src/components/landing/Navbar.tsx (1)

3-5: Import reordering aligns with ESLint config.

The blank line between external imports (next/link, react) and internal imports (@/components/...) follows the import/order rule with "newlines-between": "always" configured in the ESLint setup.

packages/kal-backend/src/lib/trpc.ts (2)

1-3: Correct use of type-only import for Context.

Since Context is only used as a type parameter for initTRPC.context<Context>(), the import type syntax is appropriate and aligns with the @typescript-eslint/consistent-type-imports rule configured in the ESLint setup.


10-24: Authentication middleware correctly implemented.

The isAuthenticated middleware properly guards protected procedures by checking for userId presence and throws an appropriate UNAUTHORIZED error. The context spreading pattern preserves existing context while ensuring type narrowing for userId.

packages/kal-frontend/src/lib/trpc.ts (1)

1-5: Import reordering and tRPC setup looks correct.

The type import for AppRouter is properly separated. The explicit type annotation on the trpc export ensures consistent typing across the frontend.

packages/kal-frontend/src/app/layout.tsx (2)

3-6: Import organization follows ESLint rules.

The blank line separating CSS import from component imports aligns with the configured import/order rule.


23-25: AuthProvider correctly handles logtoId={null}.

The AuthProvider component accepts logtoId: string | null (packages/kal-frontend/src/lib/auth-context.tsx, line 28) and properly initializes state with the provided null value. This pattern is appropriate for server-rendered layouts where authentication state is unknown at render time.

packages/kal-backend/src/lib/db.ts (1)

1-2: Correct separation of type and value imports.

Splitting Db as a type-only import and MongoClient as a runtime import follows TypeScript best practices and the consistent-type-imports rule.

packages/kal-frontend/.env.example (1)

4-11: LGTM! Environment variable structure is well-organized.

The separation between client-side (NEXT_PUBLIC_*) and server-side secrets is correct. The port changes from 3301/3302 to 3001/3002 align with the docker-compose.yml updates.

packages/kal-backend/src/routers/index.ts (1)

1-15: LGTM! Clean router composition.

The addition of halalRouter and apiKeysRouter follows the existing pattern and properly extends the backend API surface. The exported AppRouter type will automatically reflect these new routes for type-safe client usage.

packages/kal-frontend/src/app/page.tsx (1)

1-33: LGTM! Landing page composition enhanced.

The import reorganization and addition of new landing components (Features, FAQ, FinalCTA, Footer) maintain a logical flow for the landing page experience.

packages/kal-db/migrations/20241225000002_rate_limit_indexes.js (2)

6-22: LGTM! Well-designed indexes for rate limiting.

The compound unique index on (userId, date) ensures efficient lookups and prevents duplicate records per user per day. The TTL index with 7-day expiry provides automatic cleanup of stale data, which is appropriate for rate-limiting use cases.


24-40: LGTM! Resilient rollback handling.

The down() migration properly handles cases where indexes might not exist, logging informational messages rather than failing. This makes the migration safe to run multiple times.

packages/kal-backend/package.json (1)

17-17: No action required—the specified package versions are verified as free from known public vulnerabilities and are current:

  • @logto/express@3.0.12: Latest version, no CVEs recorded
  • cookie-parser@1.4.7: No known vulnerabilities
  • express-session@1.18.2: No CVEs affecting this version
packages/kal-backend/src/lib/logto.ts (1)

1-20: LGTM!

Clean Logto configuration module with appropriate environment variable handling. The validation function provides graceful degradation when credentials are missing, and the warning message clearly indicates which variables need to be set.

packages/kal-db/migrations/20241225000001_add_user_tiers.js (2)

6-23: LGTM!

The migration correctly handles idempotent collection creation and applies a sensible default tier. The updateMany with $exists: false filter ensures only users without a tier get updated, which is safe for re-runs.


25-41: Down migration is well-structured.

Good error handling for the collection drop. Since migration 20241225000002 creates indexes on rate_limit_usage, ensure migrations are rolled back in reverse order (20241225000002 down first, then this one) to avoid issues.

packages/kal-frontend/src/components/navbar.tsx (1)

7-37: LGTM!

Clean implementation of server-driven authentication in the Navbar. The inline 'use server' directives for signIn/signOut actions are valid for Next.js 15 server actions, and the optional chaining on claims handles cases where user info might be incomplete.

packages/kal-frontend/src/lib/trpc-provider.tsx (1)

17-49: LGTM!

The ref pattern is the correct approach here. Since trpcClient is created once (via useState initializer), the headers() callback captures the ref object itself, not its current value. Updating authRef.current on each render ensures the callback always accesses the latest auth state when making requests.

The credentials: "include" setting is appropriate for cookie-based authentication with cross-origin requests.

packages/kal-frontend/src/app/login-success/client.tsx (1)

10-47: LGTM!

Clean countdown implementation with proper effect separation and cleanup. The interval is correctly cleared on unmount, preventing memory leaks.

packages/kal-db/migrations/20241225000003_create_api_keys.js (1)

10-40: LGTM!

Well-designed indexes for the API keys use cases:

  • key_unique for fast authentication lookups
  • user_keys compound index optimizes listing a user's active keys by creation date
  • expiration index supports cleanup queries
packages/kal-backend/src/routers/food.ts (1)

1-92: LGTM! Collection rename is consistent.

The migration from foods to natural_foods collection is applied consistently across all public endpoints (search, all, allPaginated, categories). The field mappings remain intact.

packages/kal-frontend/src/app/dashboard/client.tsx (1)

28-50: LGTM! Clean auth synchronization pattern.

The AuthUpdater + DashboardContentWrapper pattern ensures the auth context is synchronized before rendering protected content. This prevents stale state issues with server-passed props.

packages/kal-frontend/src/app/dashboard/page.tsx (1)

36-40: The use of optional chaining (claims?.sub) is intentional and safe. Although claims is guaranteed to be defined after the isAuthenticated check, the optional chaining protects against sub potentially missing from the claims object. All downstream components—AuthUpdater and DashboardContentWrapper—are designed to gracefully handle undefined logtoId, so no guard or assertion is needed. This is defensive programming and follows the existing pattern in the codebase.

packages/kal-backend/src/index.ts (2)

95-107: CORS and middleware setup looks good.

The CORS configuration correctly filters out undefined origins and enables credentials for cookie-based auth. Middleware ordering (cors → json → cookieParser) is appropriate.


119-123: Conditional Logto route registration is well-handled.

Gracefully degrades when Logto is not configured, with appropriate logging.

packages/kal-backend/src/middleware/api-key-middleware.ts (2)

14-16: Hashing implementation is correct.

SHA-256 is appropriate for API key storage/lookup.


76-145: Middleware uses Promise-based flow correctly.

The .then()/.catch() pattern handles async validation properly. The rate limit headers are set before checking limits, and the user is attached to the request for downstream use.

packages/kal-backend/src/routers/api-keys.ts (3)

14-24: Expiration calculation is correct.

The switch handles all ApiKeyExpiration variants. Using millisecond math for 30-day month is reasonable for this use case.


82-119: API key generation implementation is solid.

The flow correctly generates a key, hashes for storage, and returns the plain key only once. The key prefix extraction and document structure align with the shared types.


1-9: No issues found. The import of RateLimitUsage from kal-shared at line 1 is correct. RateLimitUsage is defined only in packages/kal-shared/src/types/index.ts and is not duplicated locally in this file.

Likely an incorrect or invalid review comment.

packages/kal-frontend/src/lib/auth-context.tsx (2)

36-41: setAuth callback is correctly memoized.

Using useCallback with an empty dependency array ensures referential stability, preventing unnecessary re-renders in consumers.


23-48: AuthProvider structure is clean and follows React patterns.

The provider correctly initializes state from props and exposes a stable setter.

packages/kal-backend/src/lib/context.ts (1)

17-47: syncUserFromLogto implementation is solid.

The upsert pattern with $set and $setOnInsert correctly handles both create and update cases. Setting default tier to "free" aligns with the migration.

Comment thread .env.example Outdated
Comment thread .env.example Outdated
Comment thread package.json
Comment thread packages/kal-backend/src/index.ts
Comment thread packages/kal-backend/src/lib/context.ts
Comment thread packages/kal-backend/src/middleware/rate-limit.ts
Comment thread packages/kal-frontend/package.json
Comment thread packages/kal-frontend/src/app/api-docs/client.tsx
Comment thread packages/kal-frontend/src/lib/auth-context.tsx
Comment thread packages/kal-frontend/src/lib/logto.ts

@rekabytes rekabytes left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Approve

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
.env.example (1)

13-14: Ordering: Semantic grouping vs. alphabetical convention.

dotenv-linter flags suggest alphabetical ordering of keys (e.g., LOGTO_ADMIN_ENDPOINT before LOGTO_ENDPOINT). However, your current semantic grouping—organized by service (MongoDB, Logto, App)—is more intuitive for developers working with the codebase. If strict linter compliance is a project requirement, consider reordering; otherwise, the current approach is reasonable.

Also applies to: 30-32

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f302bf and c9fdc9e.

📒 Files selected for processing (1)
  • .env.example
🧰 Additional context used
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 14-14: [UnorderedKey] The LOGTO_ADMIN_ENDPOINT key should go before the LOGTO_ENDPOINT key

(UnorderedKey)


[warning] 30-30: [UnorderedKey] The BACKEND_BASE_URL key should go before the BACKEND_PORT key

(UnorderedKey)


[warning] 31-31: [UnorderedKey] The FRONTEND_PORT key should go before the NEXT_PUBLIC_API_URL key

(UnorderedKey)


[warning] 32-32: [UnorderedKey] The FRONTEND_URL key should go before the NEXT_PUBLIC_API_URL key

(UnorderedKey)

🔇 Additional comments (1)
.env.example (1)

17-18: ✅ Security fix: Credentials properly replaced with empty placeholders.

The critical issues flagged in previous reviews have been addressed—LOGTO_APP_ID, LOGTO_APP_SECRET, and SESSION_SECRET are now empty placeholders rather than containing actual secrets. This correctly prevents real credentials from being committed to version control.

Consider adding brief inline comments to guide developers on how to populate these fields (e.g., how to generate a secure SESSION_SECRET using openssl rand -base64 32 or where to find Logto credentials in the admin console).

Also applies to: 35-35

@rekabytes
rekabytes merged commit baa4742 into main Dec 25, 2025
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 15, 2026
Merged
13 tasks
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.

1 participant