Skip to content

Conversation

@pratap524
Copy link

@pratap524 pratap524 commented Dec 13, 2025

Overview
This PR introduces a fully-featured frontend application for Zplit - a modern expense splitting platform. The frontend includes comprehensive UI components for authentication, expense management, group handling, and visual expense analytics.

Features Implemented

  1. Authentication System
  • Login page with email/password authentication
  • JWT token management and secure API integration
  • Token persistence in localStorage
  • Role-based access control setup
  1. Dashboard Management
  • Ad/campaign management interface with CRUD operations
  • Search and filter functionality by status
  • Real-time data synchronization with backend
  • Modal-based inline editing for seamless UX
  • Green-highlighted "New" button for create action visibility
  1. Navigation & Layout
  • Responsive navigation bar with multi-page routing
  • Dynamic theme switching (Light/Dark mode)
  • Responsive layout switching (Portrait/Landscape)
  • Sticky navigation with gradient background
  • Neon accent styling throughout the UI
  1. Expense Management
  • Add expenses with labels and amounts
  • Expense splitting algorithms
  • Bill split result visualization
  • Item-based expense tracking with remove functionality
  1. User Management
  • Groups creation and management
  • Member invitation system
  • Account setup onboarding flow
  • User profile management
  1. Data Visualization
  • Expense graphs and analytics
  • Neon-themed visual accents
  • Clean table layouts with hover effects
  • Modal dialogs for complex interactions
  1. Design System
  • Custom CSS theme with Light/Dark variants
  • Neon accent colors (#00d4ff, #00ff41)
  • Responsive grid and container layouts
  • Comprehensive button styling (primary, secondary, danger)
  • Form input components with consistent styling
  • Shadow and glow effects for modern appearance

Technical Stack

  • Framework: React 18 with TypeScript
  • Routing: React Router v6
  • HTTP Client: Axios with Bearer token authentication
  • Styling: Custom CSS with CSS variables for theming
  • State Management: React hooks (useState, useContext)
  • Build Tool: Vite for fast development and optimized builds

Key Components

  • NavBar.tsx - Navigation with theme and layout controls
  • Login.tsx - Authentication interface
  • Dashboard.tsx - Main ad management system
  • AddExpenses.tsx - Expense tracking and splitting
  • Groups.tsx - Group management
  • SplitButton.tsx & SplitModal.tsx - Bill splitting UI
  • ThemeProvider - Context-based theme management

UI/UX Improvements

  • Green accent on "New" button for better visual hierarchy
  • Neon glow effects for interactive elements
  • Smooth transitions and hover states
  • Clear visual feedback for user actions
  • Responsive design for different screen orientations
  • Accessibility features (aria-labels, focus states)

Integration

  • Fully integrated with backend API at http://localhost:4000
  • Configurable API endpoint via VITE_API_URL environment variable
  • Proper error handling and user feedback
  • Secure token management and authentication flow

Files Changed

  • Added 11 frontend files including pages, components, styles, and configuration
  • Created theme system with dark/light mode support
  • Implemented complete routing structure
  • Added comprehensive CSS styling with animations and effects

Testing

  • Frontend can be tested by running npm run dev in the frontend directory
  • Test with backend running on port 4000
  • Access at http://localhost:5173
  • Default login flow available on /login route
  • All main routes accessible via navigation bar

Notes

  • Node modules are excluded from repository (configured in .gitignore)
  • Uses environment variables for API configuration
  • Theme persists across browser sessions via DOM attributes
  • Token-based authentication for all API requests

Summary by CodeRabbit

  • New Features

    • Added full-stack application with user authentication, login, and registration system.
    • Implemented role-based access control for Admin and Client users.
    • Added advertisement management system with create, read, update, and delete capabilities.
    • Introduced deep link asset support for Android and iOS platforms.
    • Enabled Docker containerization and Compose orchestration for local development and deployment.
  • Documentation

    • Completely restructured README with updated setup, deployment, and security guidance.
    • Added design guidelines and theme specifications.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 13, 2025

Walkthrough

This PR restructures the project from a LaTeX-centric setup to a full-stack application featuring a Node.js/Express backend (PostgreSQL via Prisma) and React/Vite frontend. Introduces authentication, role-based access control, advertisement CRUD operations, and deep-link asset management with Docker containerization and environment-driven configuration.

Changes

Cohort / File(s) Summary
Project Configuration & Build
.gitignore, backend/tsconfig.json, docker-compose.yml
Updated .gitignore to prioritize build artifacts and project dependencies over LaTeX patterns; added TypeScript compiler configuration for backend targeting ES2020 with strict type checking; introduced multi-service Docker Compose setup with PostgreSQL, Node.js backend, and Vite frontend.
Backend Project Setup
backend/package.json, backend/Dockerfile
Added Node.js project manifest with scripts (dev, build, start, Prisma migration/seed), dependencies (express, bcrypt, jsonwebtoken, Prisma, zod), and dev tooling (TypeScript, ts-node-dev); created Alpine-based Dockerfile for containerization with multi-step build and Prisma client generation.
Backend Configuration & Core Services
backend/src/config.ts, backend/src/prisma.ts, backend/src/services/cache.ts
Implemented centralized environment configuration with validation for JWT_SECRET and DATABASE_URL (fail-fast on missing vars); created Prisma client singleton; initialized in-memory cache service with 60-second TTL.
Backend Authentication & Authorization
backend/src/middlewares/auth.ts, backend/src/routes/auth.ts
Added middleware for JWT validation and role-based access control (authMiddleware, adminOnly, clientOrAdmin); implemented authentication routes for client registration, admin registration (invite-key protected), login (JWT issuance), and user profile fetch; includes bcrypt password hashing and Zod input validation.
Backend Advertisement Management
backend/src/routes/ads.ts
Introduced full CRUD operations for advertisements with role-based filtering; admins view all ads, clients view only their own; includes status/search filtering, ownership enforcement, and comprehensive error handling.
Backend Deep-Link Management
backend/src/routes/admin.ts, backend/src/routes/deeplinks.ts
Added admin endpoints to create and update deep-link assets with platform-specific content and cache invalidation; created public endpoints to serve cached Android assetlinks.json and iOS apple-app-site-association files.
Backend Server & Database
backend/src/server.ts, backend/prisma/schema.prisma, backend/prisma/seed.ts, backend/.env.example
Set up Express server with CORS and router mounting; defined Prisma schema with Client, User, Advertisement, and DeepLinkAsset models plus enums (AdStatus, UserRole, Platform); implemented seeding script to initialize admin user, client, and demo advertisements; added example environment configuration.
Frontend Setup & Deployment
frontend/index.html, frontend/Dockerfile, frontend/nginx.conf
Created HTML entry point mounting React app via /src/main.tsx; implemented multi-stage Dockerfile using Node builder and nginx production image; configured nginx for SPA client-side routing, gzip compression, security headers, and asset caching.
Documentation
README.md, design/DesignGuidelines.md
Completely rewrote README from mobile-centric to full-stack focus: replaced Flutter/P2P content with backend/frontend stack details, environment configuration guidance, security considerations, and deployment instructions; added design guidelines documenting minimal theme concept and UX requirements.

Sequence Diagrams

sequenceDiagram
    participant Client
    participant Auth Routes
    participant Prisma
    participant JWT Service
    participant Database

    Client->>Auth Routes: POST /register (email, password, clientName)
    Auth Routes->>Prisma: upsert Client by email
    Prisma->>Database: check/create Client
    Database-->>Prisma: Client record
    Auth Routes->>Auth Routes: hash password (bcrypt)
    Auth Routes->>Prisma: create User (role: CLIENT)
    Prisma->>Database: insert User
    Database-->>Prisma: User record
    Prisma-->>Auth Routes: User with id, email, role, clientId
    Auth Routes-->>Client: { id, email, role, clientId }

    rect rgba(100, 150, 200, 0.2)
    Note over Client,Database: Login & Token Generation
    Client->>Auth Routes: POST /login (email, password)
    Auth Routes->>Prisma: find User by email
    Prisma->>Database: query User
    Database-->>Prisma: User with password hash
    Auth Routes->>Auth Routes: compare password (bcrypt)
    Auth Routes->>JWT Service: sign JWT { userId }
    JWT Service-->>Auth Routes: token
    Auth Routes-->>Client: { token, role, clientId }
    end

    rect rgba(100, 200, 100, 0.2)
    Note over Client,Database: Protected Route Access
    Client->>Auth Routes: GET /me (Authorization: Bearer token)
    Auth Routes->>Auth Routes: verify JWT
    Auth Routes->>Prisma: fetch User by userId
    Prisma->>Database: query User with client relation
    Database-->>Prisma: User + Client data
    Prisma-->>Auth Routes: User record
    Auth Routes-->>Client: { id, email, role, client }
    end
Loading
sequenceDiagram
    participant Admin
    participant Admin Routes
    participant Cache Service
    participant Prisma
    participant Database
    participant Public Client
    participant Deeplink Routes

    rect rgba(100, 150, 200, 0.2)
    Note over Admin,Database: Admin Creates/Updates Deep-Link Asset
    Admin->>Admin Routes: POST /deeplink { platform: ANDROID, content }
    Admin Routes->>Prisma: create DeepLinkAsset
    Prisma->>Database: insert asset
    Database-->>Prisma: created asset
    Admin Routes->>Cache Service: invalidate ANDROID_ASSETLINKS
    Cache Service-->>Admin Routes: invalidated
    Admin Routes-->>Admin: { id, platform, content }
    end

    rect rgba(100, 200, 100, 0.2)
    Note over Public Client,Deeplink Routes: Public Serves Cached Asset
    Public Client->>Deeplink Routes: GET /.well-known/assetlinks.json
    Deeplink Routes->>Cache Service: get ANDROID_ASSETLINKS
    alt Cache Hit
        Cache Service-->>Deeplink Routes: cached content
    else Cache Miss
        Deeplink Routes->>Prisma: fetch DeepLinkAsset (platform: ANDROID)
        Prisma->>Database: query asset
        Database-->>Prisma: asset
        Deeplink Routes->>Cache Service: set ANDROID_ASSETLINKS
        Cache Service-->>Deeplink Routes: cached
    end
    Deeplink Routes-->>Public Client: application/json { assetlinks }
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–75 minutes

  • Backend authentication system: JWT handling, role-based middleware, and multiple registration flows require careful verification of security practices (password hashing, token expiry, invitation key validation).
  • Prisma schema and relationships: Verify model definitions, foreign key constraints, and enum usage; seed script behavior with environment variables.
  • Cache invalidation logic: Ensure platform-specific cache keys are correctly invalidated when deep-link assets are updated.
  • Error handling and validation: Check Zod schema usage, error responses, and edge cases across all routes (duplicate emails, missing fields, authorization failures).
  • Docker and deployment configuration: Multi-stage builds, environment variable handling in docker-compose, and nginx SPA routing.
  • Duplicate export in auth.ts: Note the duplicate export default router at the end of backend/src/routes/auth.ts that should be removed.

Poem

🐰 Hop along, the schemas spring alive,
PostgreSQL and Prisma help us thrive!
From LaTeX dust to TypeScript gleam,
Auth flows smoothly like a dev's sweet dream. 🚀
Cache invalidates, deep links align—
Full-stack magic, hop-hip-hooray divine!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title claims to add frontend features (Dashboard, Auth, Expense Management UI), but the changeset primarily adds backend infrastructure (Node.js/Express server, Prisma ORM, database models, authentication middleware, API routes) and database configuration, with only minimal frontend setup (Dockerfile, nginx config, HTML entry point). Revise the title to reflect the actual primary change. Consider: 'feat: Add complete backend with Prisma ORM, authentication, and API routes' or 'feat: Add backend infrastructure and database schema for Zplit.' If frontend features are genuinely the main focus, provide the actual frontend implementation files.
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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

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

@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: 6

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (16)
backend/.env-1-1 (1)

1-1: Hardcoded database credentials pose a security risk.

The DATABASE_URL contains default credentials (postgres:postgres) that are commonly known and easily guessable. If this configuration is used in any non-local environment, it creates a significant security vulnerability.

Consider:

  • Using stronger credentials even for local development
  • Documenting that these are development-only defaults
  • Ensuring production environments use secure credential management (e.g., secrets managers, environment-specific configuration)
backend/Dockerfile-13-13 (1)

13-13: Suppressing Prisma generation errors can hide critical failures.

The || true causes the build to continue even if Prisma client generation fails. This can lead to runtime errors when the application tries to use the Prisma client, making issues harder to debug.

Remove the error suppression to fail fast on Prisma issues:

-RUN npx prisma generate || true
+RUN npx prisma generate

If there are legitimate cases where Prisma generation might not be needed, handle them explicitly with proper error messaging.

backend/package.json-20-20 (1)

20-20: Move prisma CLI to devDependencies.

The prisma package is a CLI tool used for migrations and schema management during development, not at runtime. Only @prisma/client (line 21) is needed in production dependencies.

Apply this change:

   "dependencies": {
     "bcrypt": "^5.1.0",
     "cors": "^2.8.5",
     "dotenv": "^16.0.3",
     "express": "^4.18.2",
     "jsonwebtoken": "^9.0.0",
     "node-cache": "^5.1.2",
-    "prisma": "^5.6.0",
     "@prisma/client": "^5.6.0",
     "zod": "^3.20.2"
   },
   "devDependencies": {
     "@types/bcrypt": "^5.0.0",
     "@types/cors": "^2.8.12",
     "@types/express": "^4.17.17",
     "@types/jsonwebtoken": "^9.0.2",
+    "prisma": "^5.6.0",
     "ts-node": "^10.9.1",
     "ts-node-dev": "^2.0.0",
     "typescript": "^5.2.2"
   }
README.md-1-9 (1)

1-9: README scope seems inconsistent with the PR description—please align.

Line 3 says this is an app for “managing advertisements and hosting deep-linking assets”, and the documented endpoints are ads/deeplinks-focused—this conflicts with the PR objectives you provided (expense splitting features). Please confirm the intended scope and update either README (or PR description) so reviewers/users aren’t misled.

backend/src/server.ts-1-12 (1)

1-12: Load .env before importing route modules (current order can break config).

dotenv.config() is called after ./routes/* imports (Line 4–7 vs Line 9). If any route/middleware reads process.env at module scope, it may run before .env is loaded.

-import express from 'express';
-import cors from 'cors';
-import dotenv from 'dotenv';
+import 'dotenv/config';
+import express from 'express';
+import cors from 'cors';
 import authRouter from './routes/auth';
 import adsRouter from './routes/ads';
 import deepLinksRouter from './routes/deeplinks';
 import adminRouter from './routes/admin';

-dotenv.config();
 const app = express();
 const port = process.env.PORT || 4000;
docker-compose.yml-3-26 (1)

3-26: DB readiness: depends_on won’t prevent backend startup races—add a healthcheck/wait.

As-is, backend can run migrations/generate/client before Postgres is ready, causing intermittent failures.

 services:
   db:
     image: postgres:15
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U postgres -d zplit_db"]
+      interval: 5s
+      timeout: 3s
+      retries: 20
@@
   backend:
@@
-    depends_on:
-      - db
+    depends_on:
+      db:
+        condition: service_healthy
backend/src/routes/deeplinks.ts-12-41 (1)

12-41: Handle async errors (today any prisma failure can bubble as an unhandled rejection).

Both handlers are async (Line 12, 29) but don’t guard await prisma... (Line 21, 36). Wrap in try/catch (or use an async error middleware wrapper) and return a consistent 500.

 router.get('/assetlinks.json', async (req, res) => {
-  // Android assetlinks
-  const cacheKey = 'ANDROID_ASSETLINKS';
-  const cached = cache.get(cacheKey);
-  if (cached) {
-    res.set('Content-Type', 'application/json');
-    return res.json(cached);
-  }
-
-  const record = await prisma.deepLinkAsset.findFirst({ where: { platform: 'ANDROID' }, orderBy: { updatedAt: 'desc' } });
-  if (!record) return res.status(404).json({ message: 'Not configured' });
-
-  cache.set(cacheKey, record.content);
-  res.set('Content-Type', 'application/json');
-  res.json(record.content);
+  try {
+    const cacheKey = 'ANDROID_ASSETLINKS';
+    const cached = cache.get(cacheKey);
+    if (cached !== undefined) {
+      res.set('Content-Type', 'application/json');
+      return res.json(cached);
+    }
+
+    const record = await prisma.deepLinkAsset.findFirst({
+      where: { platform: 'ANDROID' },
+      orderBy: { updatedAt: 'desc' },
+    });
+    if (!record) return res.status(404).json({ message: 'Not configured' });
+
+    cache.set(cacheKey, record.content);
+    res.set('Content-Type', 'application/json');
+    return res.json(record.content);
+  } catch (err) {
+    return res.status(500).json({ message: 'Internal error' });
+  }
 });
backend/prisma/seed.ts-17-25 (1)

17-25: Fix seed idempotency, remove error suppression, and correct Android content shape.

  • createMany (lines 17–20) has no uniqueness constraint and will create duplicate rows on every run.
  • DeepLinkAsset upserts by id (lines 23–24), which works only if records already exist with those IDs. The ANDROID record (line 23) also has inconsistent content shapes: update uses { apps: [] } but create uses { statements: [] }, while the IOS record correctly uses { applinks: {} } for both paths.
  • Both upserts silently swallow errors via .catch(()=>{}), which can mask failures and prevent detection of data issues.

Replace createMany with upserts keyed on stable fields (e.g., ad_name or target_url for ads), add a unique constraint to DeepLinkAsset's platform field if it should be singleton per platform, fix the ANDROID content shape to match the update path, and remove error suppression to surface real issues.

backend/src/routes/admin.ts-27-40 (1)

27-40: Return 400/404 for PUT /deeplink/:id (NaN + record-not-found shouldn’t become 500).
prisma.deepLinkAsset.update() throws when the record doesn’t exist; that should map to 404.

 router.put('/deeplink/:id', async (req, res) => {
   const id = parseInt(req.params.id, 10);
+  if (!Number.isFinite(id)) return res.status(400).json({ message: 'Invalid id' });
   const { content } = req.body;
   try {
     const updated = await prisma.deepLinkAsset.update({ where: { id }, data: { content } });
     // invalidate caches by detecting platform
     if (updated.platform === 'ANDROID') cache.del('ANDROID_ASSETLINKS');
     if (updated.platform === 'IOS') cache.del('IOS_AASA');
     res.json(updated);
   } catch (err) {
     console.error(err);
+    // Prisma: record not found => P2025
+    if ((err as any)?.code === 'P2025') return res.status(404).json({ message: 'Deep link asset not found' });
     res.status(500).json({ message: 'Error updating deep link asset' });
   }
 });
backend/src/routes/ads.ts-82-90 (1)

82-90: DELETE /ads/:id: add try/catch + validate :id (DB errors currently escape).
A failing delete (FK constraint, transient DB error) will currently bubble up.

 router.delete('/:id', async (req, res) => {
   const id = parseInt(req.params.id, 10);
-  const ad = await prisma.advertisement.findUnique({ where: { id } });
-  if (!ad) return res.status(404).json({ message: 'Ad not found' });
-  if (req.user.role === 'CLIENT' && ad.clientId !== req.user.clientId) return res.status(403).json({ message: 'Forbidden' });
-  await prisma.advertisement.delete({ where: { id } });
-  res.json({ ok: true });
+  if (!Number.isFinite(id)) return res.status(400).json({ message: 'Invalid id' });
+  try {
+    const ad = await prisma.advertisement.findUnique({ where: { id } });
+    if (!ad) return res.status(404).json({ message: 'Ad not found' });
+    if (req.user.role === 'CLIENT' && ad.clientId !== req.user.clientId) return res.status(403).json({ message: 'Forbidden' });
+    await prisma.advertisement.delete({ where: { id } });
+    res.json({ ok: true });
+  } catch (err) {
+    console.error(err);
+    res.status(500).json({ message: 'Error deleting ad' });
+  }
 });
backend/src/routes/ads.ts-31-39 (1)

31-39: Validate :id before querying (NaN should be 400, not 500/undefined behavior).
parseInt() can produce NaN; findUnique({ id: NaN }) may throw or behave unexpectedly, and there’s no try/catch here.

 router.get('/:id', async (req, res) => {
   const id = parseInt(req.params.id, 10);
+  if (!Number.isFinite(id)) return res.status(400).json({ message: 'Invalid id' });
   const ad = await prisma.advertisement.findUnique({ where: { id } });
   if (!ad) return res.status(404).json({ message: 'Ad not found' });
   // Client users can only access their own
   if (req.user.role === 'CLIENT' && ad.clientId !== req.user.clientId) return res.status(403).json({ message: 'Forbidden' });
   res.json(ad);
 });
backend/src/routes/admin.ts-12-25 (1)

12-25: Validate platform as enum + don’t treat empty JSON as missing.
Right now any string for platform will reach Prisma (500 instead of 400), and !content is a bit ambiguous for JSON payloads.

 router.post('/deeplink', async (req, res) => {
   const { platform, content } = req.body; // platform should be 'ANDROID' or 'IOS', content is JSON
-  if (!platform || !content) return res.status(400).json({ message: 'platform+content required' });
+  if (typeof platform !== 'string') return res.status(400).json({ message: 'platform required' });
+  if (platform !== 'ANDROID' && platform !== 'IOS') return res.status(400).json({ message: 'Invalid platform' });
+  if (content === undefined) return res.status(400).json({ message: 'content required' });
   try {
     const record = await prisma.deepLinkAsset.create({ data: { platform, content } });
     // Invalidate caches for the platform
     if (platform === 'ANDROID') cache.del('ANDROID_ASSETLINKS');
     if (platform === 'IOS') cache.del('IOS_AASA');
     res.json(record);
   } catch (err) {
     console.error(err);
     res.status(500).json({ message: 'Error creating deep link asset' });
   }
 });
backend/src/routes/ads.ts-63-80 (1)

63-80: PUT /ads/:id: validate :id + handle invalid dates similarly to create.
Same NaN/Invalid Date risks; plus status can be invalid enum (causing 500).

 router.put('/:id', async (req, res) => {
   const id = parseInt(req.params.id, 10);
+  if (!Number.isFinite(id)) return res.status(400).json({ message: 'Invalid id' });
   const ad = await prisma.advertisement.findUnique({ where: { id } });
   if (!ad) return res.status(404).json({ message: 'Ad not found' });
   if (req.user.role === 'CLIENT' && ad.clientId !== req.user.clientId) return res.status(403).json({ message: 'Forbidden' });
   const { ad_name, target_url, start_date, end_date, status } = req.body;
   try {
+    const parsedStatus = typeof status === 'string' ? status.toUpperCase() : status;
+    if (parsedStatus && !['ACTIVE','PAUSED','ARCHIVED'].includes(parsedStatus)) {
+      return res.status(400).json({ message: 'Invalid status' });
+    }
+    const sd = start_date ? new Date(start_date) : undefined;
+    const ed = end_date ? new Date(end_date) : undefined;
+    if (sd && !Number.isFinite(sd.getTime())) return res.status(400).json({ message: 'Invalid start_date' });
+    if (ed && !Number.isFinite(ed.getTime())) return res.status(400).json({ message: 'Invalid end_date' });
     const updated = await prisma.advertisement.update({
       where: { id },
-      data: { ad_name, target_url, start_date: start_date ? new Date(start_date) : undefined, end_date: end_date ? new Date(end_date) : undefined, status },
+      data: { ad_name, target_url, start_date: sd, end_date: ed, status: parsedStatus },
     });
     res.json(updated);
   } catch (err) {
     console.error(err);
     res.status(500).json({ message: 'Error updating ad' });
   }
 });
backend/src/routes/auth.ts-25-44 (1)

25-44: Use a transaction for (Client create) + (User create) to avoid partial state.
If user creation fails after client creation, you’ll leave an orphaned Client.

-    let clientId: number | null = null;
-    // Create a client for role CLIENT if it doesn't exist
-    if (role === 'CLIENT') {
-      if (!clientName) return res.status(400).json({ message: 'clientName required for CLIENT role' });
-      const existing = await prisma.client.findUnique({ where: { email } });
-      const client = existing ?? (await prisma.client.create({ data: { name: clientName, email } }));
-      clientId = client.id;
-    }
-
-    const hashed = await bcrypt.hash(password, 10);
-    const user = await prisma.user.create({
-      data: {
-        email,
-        password: hashed,
-        role,
-        clientId,
-      },
-    });
+    const hashed = await bcrypt.hash(password, 10);
+    const user = await prisma.$transaction(async (tx) => {
+      let clientId: number | null = null;
+      if (role === 'CLIENT') {
+        if (!clientName) throw Object.assign(new Error('clientName required'), { httpStatus: 400 });
+        const existing = await tx.client.findUnique({ where: { email } });
+        const client = existing ?? (await tx.client.create({ data: { name: clientName, email } }));
+        clientId = client.id;
+      }
+      return tx.user.create({ data: { email, password: hashed, role, clientId } });
+    });
     res.json({ id: user.id, email: user.email, role: user.role, clientId: user.clientId });
   } catch (err: any) {
     console.error(err);
+    if (err.httpStatus) return res.status(err.httpStatus).json({ message: err.message });
     if (err.code === 'P2002') return res.status(400).json({ message: 'Email already exists' });
     res.status(500).json({ message: 'Error registering user' });
   }
backend/src/routes/ads.ts-41-61 (1)

41-61: Reject invalid dates / invalid enum status up-front in POST /ads (avoid storing “Invalid Date” / runtime 500s).
new Date(start_date) succeeds syntactically even for garbage input, yielding Invalid Date.

 router.post('/', async (req, res) => {
   const { ad_name, target_url, start_date, end_date, status } = req.body;
   const role = req.user.role;
   const clientId = req.user.clientId;
   try {
-    const data: any = { ad_name, target_url, start_date: new Date(start_date), end_date: new Date(end_date), status };
+    const parsedStatus = typeof status === 'string' ? status.toUpperCase() : status;
+    if (parsedStatus && !['ACTIVE','PAUSED','ARCHIVED'].includes(parsedStatus)) {
+      return res.status(400).json({ message: 'Invalid status' });
+    }
+    const sd = new Date(start_date);
+    const ed = new Date(end_date);
+    if (!Number.isFinite(sd.getTime()) || !Number.isFinite(ed.getTime())) {
+      return res.status(400).json({ message: 'Invalid start_date/end_date' });
+    }
+    const data: any = { ad_name, target_url, start_date: sd, end_date: ed, status: parsedStatus };
     if (role === 'ADMIN' && req.body.clientId) {
       data.clientId = req.body.clientId;
     } else if (role === 'CLIENT') {
       data.clientId = clientId;
     } else {
       return res.status(400).json({ message: 'Client ID required for admin ad creation' });
     }
     const ad = await prisma.advertisement.create({ data });
     res.json(ad);
   } catch (err) {
     console.error(err);
     res.status(500).json({ message: 'Error creating ad' });
   }
 });
backend/src/routes/ads.ts-7-29 (1)

7-29: Add consistent async error handling + validate status/search inputs (avoid unhandled rejections / accidental 500s).
Right now, the GET /ads handler has no try/catch and uses where: any, so DB/query errors can escape Express’s normal error flow and crash/poison the request lifecycle.

 import express from 'express';
 import prisma from '../prisma';
 import { authMiddleware } from '../middlewares/auth';

 const router = express.Router();

 // All routes require auth
 router.use(authMiddleware);

+function parseAdStatus(s: unknown) {
+  if (typeof s !== 'string') return undefined;
+  const v = s.toUpperCase();
+  return (v === 'ACTIVE' || v === 'PAUSED' || v === 'ARCHIVED') ? v : null;
+}
+
 // GET /ads - list ads (admins see all, clients only their own)
 router.get('/', async (req, res) => {
-  const role = req.user.role;
-  const clientId = req.user.clientId;
-  const { status, search } = req.query;
-  const where: any = {};
-  if (role === 'CLIENT') where.clientId = clientId;
-  if (status) where.status = { equals: (status as string).toUpperCase() };
-  if (search) {
-    where.OR = [
-      { ad_name: { contains: search as string, mode: 'insensitive' } },
-      { target_url: { contains: search as string, mode: 'insensitive' } },
-    ];
-  }
-  const ads = await prisma.advertisement.findMany({
-    where,
-    orderBy: { createdAt: 'desc' },
-  });
-  res.json(ads);
+  try {
+    const role = req.user.role;
+    const clientId = req.user.clientId;
+    const { status, search } = req.query;
+
+    const where: any = {};
+    if (role === 'CLIENT') where.clientId = clientId;
+
+    const parsedStatus = parseAdStatus(status);
+    if (parsedStatus === null) return res.status(400).json({ message: 'Invalid status' });
+    if (parsedStatus) where.status = parsedStatus;
+
+    if (typeof search === 'string' && search.trim()) {
+      where.OR = [
+        { ad_name: { contains: search, mode: 'insensitive' } },
+        { target_url: { contains: search, mode: 'insensitive' } },
+      ];
+    }
+
+    const ads = await prisma.advertisement.findMany({ where, orderBy: { createdAt: 'desc' } });
+    res.json(ads);
+  } catch (err) {
+    console.error(err);
+    res.status(500).json({ message: 'Error fetching ads' });
+  }
 });
🟡 Minor comments (6)
frontend/index.html-2-2 (1)

2-2: Add lang attribute for accessibility and SEO.

The <html> element is missing the lang attribute, which is important for screen readers, search engines, and browser translation features.

Apply this fix:

-<html>
+<html lang="en">
backend/package.json-13-22 (1)

13-22: Update outdated dependencies for maintainability.

Most dependencies are currently secure with no known vulnerabilities. However, several packages are significantly outdated: bcrypt (5.1.0 → 6.0.0), dotenv (16.0.3 → 17.2.3), express (4.18.2 → 5.2.1), jsonwebtoken (9.0.0 → 9.0.3), prisma (5.6.0 → 7.1.0), @prisma/client (5.6.0 → 7.1.0), and zod (3.20.2 → 4.1.13). Consider updating these packages to receive bug fixes, performance improvements, and new features, especially prisma and zod which are two major versions behind.

design/DesignGuidelines.md-66-71 (1)

66-71: Update/clarify the design deadline (currently in the past).

This PR was created on 2025-12-13, but the document states Deadline: 1st December 2025 (Line 69). Either update the date or clarify it was a past milestone.

README.md-73-95 (1)

73-95: Update/clarify the design deadline (currently in the past).

README says Design deadline: 1st December 2025 (Line 92), but the PR was created 2025-12-13. Please update or clarify.

backend/src/routes/deeplinks.ts-5-8 (1)

5-8: Remove duplicated comments and dead commented-out code.

Line 5–8 contains duplicate comment lines and an obsolete commented-out cache export. Keep the module clean to avoid confusion.

design/DesignGuidelines.md-18-22 (1)

18-22: Complete the “4 modes for each screen” requirement (currently unfinished).

Line 20–22 says “provide 4 modes” but doesn’t list them in this doc (even though README later mentions them). Add the explicit list here to make this doc standalone.

🧹 Nitpick comments (15)
backend/Dockerfile (2)

1-19: Consider multi-stage build to reduce production image size.

The current Dockerfile installs all dependencies (including devDependencies) and keeps them in the final image. A multi-stage build can reduce the production image size and attack surface by excluding development dependencies.

Consider this multi-stage approach:

# Build stage
FROM node:20-alpine AS builder
WORKDIR /usr/src/app
COPY package.json package-lock.json* ./
RUN npm ci
COPY . .
RUN npx prisma generate
RUN npm run build

# Production stage
FROM node:20-alpine
WORKDIR /usr/src/app
COPY package.json package-lock.json* ./
RUN npm ci --only=production
COPY --from=builder /usr/src/app/dist ./dist
COPY --from=builder /usr/src/app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /usr/src/app/prisma ./prisma
EXPOSE 4000
CMD ["node", "dist/server.js"]

1-19: Run container as non-root user for better security.

The container runs as root by default, which violates security best practices and increases the potential impact of container breakout vulnerabilities.

Add a non-root user:

FROM node:20-alpine
WORKDIR /usr/src/app

# Create non-root user
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001

COPY package.json package-lock.json* ./
RUN npm install
COPY . .
RUN npx prisma generate
RUN npm run build

# Change ownership
RUN chown -R nodejs:nodejs /usr/src/app

USER nodejs
EXPOSE 4000
CMD ["node", "dist/server.js"]
backend/tsconfig.json (1)

1-12: Consider adding explicit include/exclude patterns.

While the current configuration works, explicitly specifying which files to compile can prevent accidental compilation of test files, build artifacts, or other non-source files.

   "compilerOptions": {
     "target": "ES2020",
     "module": "CommonJS",
     "rootDir": "src",
     "outDir": "dist",
     "esModuleInterop": true,
     "forceConsistentCasingInFileNames": true,
     "strict": true,
     "skipLibCheck": true
-  }
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
 }
.gitignore (1)

1-1: Minor redundancy in node_modules patterns.

Line 1 (node_modules/) at the root already covers frontend/node_modules/ and backend/node_modules/ due to how .gitignore patterns work. The specific subdirectory entries on lines 10-11 are redundant.

You can simplify by removing the redundant lines:

 node_modules/
 dist/
 build/
 .env
 .env.local
 *.log
 .DS_Store
 .vscode/
 .idea/
-frontend/node_modules/
-backend/node_modules/

Also applies to: 10-11

backend/src/prisma.ts (2)

1-4: Add graceful shutdown handling for Prisma client.

The Prisma client should be properly disconnected when the application shuts down to ensure connections are cleaned up and prevent connection leaks.

Consider adding shutdown handling:

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Graceful shutdown
const shutdown = async () => {
  await prisma.$disconnect();
  process.exit(0);
};

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);

export default prisma;

Alternatively, if shutdown is handled centrally in server.ts, document that expectation and ensure prisma.$disconnect() is called there.


3-3: Consider singleton pattern for development hot-reload.

In development with hot module reloading (ts-node-dev), multiple PrismaClient instances can be created, leading to connection pool exhaustion and warnings.

Implement a development-safe singleton:

import { PrismaClient } from '@prisma/client';

declare global {
  var prisma: PrismaClient | undefined;
}

const prisma = global.prisma || new PrismaClient();

if (process.env.NODE_ENV !== 'production') {
  global.prisma = prisma;
}

export default prisma;
frontend/Dockerfile (1)

5-5: Use npm ci for more deterministic builds.

npm install can update dependencies based on semver ranges, leading to inconsistent builds. npm ci uses the exact versions from package-lock.json, ensuring reproducible builds.

-RUN npm install
+RUN npm ci
backend/src/server.ts (1)

13-15: Add minimal API hardening knobs (CORS scoping + JSON size limit).

app.use(cors()) (Line 13) defaults to * and express.json() (Line 14) defaults can accept large payloads. Even for “example apps”, adding guardrails helps avoid surprises.

-app.use(cors());
-app.use(express.json());
+app.use(
+  cors({
+    origin: process.env.CORS_ORIGIN?.split(',') ?? true,
+    credentials: true,
+  }),
+);
+app.use(express.json({ limit: '1mb' }));
docker-compose.yml (1)

15-26: Avoid npm install on every container start; don’t clobber node_modules with bind mounts.

Current pattern (Line 24–25) is slow and can produce odd host/container state. Consider a named volume for /usr/src/app/node_modules and/or move install/generate into the image build.

backend/src/routes/deeplinks.ts (1)

12-41: Optional: add Cache-Control headers to match the in-memory TTL.

Since you’re caching for 60s, consider res.set('Cache-Control', 'public, max-age=60') on both endpoints for better client/CDN behavior.

backend/src/routes/admin.ts (1)

42-45: Consider pagination for GET /deeplink if this can grow unbounded.
Admin endpoints still need guardrails to avoid accidental heavy reads.

backend/src/middlewares/auth.ts (1)

5-13: Avoid req.user?: any; define a concrete RequestUser shape (prevents accidental misuse).
This will help route code avoid any-driven bugs.

 declare global {
   namespace Express {
     interface Request {
-      user?: any;
+      user?: { id: number; role: 'ADMIN' | 'CLIENT'; clientId: number | null };
     }
   }
 }
backend/src/routes/auth.ts (1)

52-67: Consider basic brute-force protection on /login (rate limit / lockout).
Not required for functionality, but this is a common production hardening step.

backend/prisma/schema.prisma (2)

10-40: Add indexes on FK columns used for filtering (clientId) to keep list endpoints fast.
Your routes frequently filter by clientId; indexing is low-effort and high-impact.

 model User {
   id        Int      @id @default(autoincrement())
   email     String   @unique
   password  String
   role      UserRole
   client    Client?  @relation(fields: [clientId], references: [id])
   clientId  Int?
   createdAt DateTime @default(now())
+
+  @@index([clientId])
 }

 model Advertisement {
   id         Int      @id @default(autoincrement())
   ad_name    String
   target_url String
   start_date DateTime
   end_date   DateTime
   status     AdStatus  @default(ACTIVE)
   client     Client    @relation(fields: [clientId], references: [id])
   clientId   Int
   createdAt  DateTime  @default(now())
   updatedAt  DateTime  @updatedAt
+
+  @@index([clientId])
+  @@index([status])
 }

10-40: Consider explicit referential actions for Client relations (avoid surprises on deletes).
Depending on your intended behavior, set onDelete: Restrict|Cascade|SetNull for User.client / Advertisement.client.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a490608 and 1e605ec.

⛔ Files ignored due to path filters (280)
  • frontend/node_modules/.bin/baseline-browser-mapping is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/baseline-browser-mapping.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/baseline-browser-mapping.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/browserslist is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/browserslist.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/browserslist.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/esbuild is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/esbuild.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/esbuild.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/jsesc is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/jsesc.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/jsesc.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/json5 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/json5.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/json5.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/loose-envify is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/loose-envify.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/loose-envify.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/nanoid is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/nanoid.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/nanoid.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/parser is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/parser.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/parser.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/rollup is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/rollup.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/rollup.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/semver is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/semver.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/semver.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/tsc is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/tsc.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/tsc.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/tsserver is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/tsserver.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/tsserver.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/update-browserslist-db is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/update-browserslist-db.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/update-browserslist-db.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/vite is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/vite.cmd is excluded by !**/node_modules/**
  • frontend/node_modules/.bin/vite.ps1 is excluded by !**/node_modules/**
  • frontend/node_modules/.package-lock.json is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/_metadata.json is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/axios.js is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/axios.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/.vite/deps/chunk-6BKLQ22S.js is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/chunk-6BKLQ22S.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/.vite/deps/chunk-DRWLMN53.js is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/chunk-DRWLMN53.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/.vite/deps/chunk-G3PMV62Z.js is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/chunk-G3PMV62Z.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/.vite/deps/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/react-dom_client.js is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/react-dom_client.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/.vite/deps/react-router-dom.js is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/react-router-dom.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/.vite/deps/react.js is excluded by !**/node_modules/**
  • frontend/node_modules/.vite/deps/react.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/code-frame/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/code-frame/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/code-frame/lib/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/code-frame/lib/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/code-frame/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/corejs2-built-ins.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/corejs3-shipped-proposals.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/data/corejs2-built-ins.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/data/corejs3-shipped-proposals.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/data/native-modules.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/data/overlapping-plugins.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/data/plugin-bugfixes.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/data/plugins.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/native-modules.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/overlapping-plugins.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/plugin-bugfixes.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/compat-data/plugins.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/cache-contexts.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/cache-contexts.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/caching.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/caching.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/config-chain.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/config-chain.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/config-descriptors.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/config-descriptors.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/configuration.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/configuration.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/import.cjs is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/import.cjs.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/index-browser.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/index-browser.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/module-types.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/module-types.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/package.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/package.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/plugins.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/plugins.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/types.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/types.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/files/utils.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/files/utils.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/full.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/full.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/helpers/config-api.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/helpers/config-api.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/helpers/deep-array.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/helpers/deep-array.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/helpers/environment.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/helpers/environment.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/item.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/item.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/partial.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/partial.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/pattern-to-regex.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/pattern-to-regex.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/plugin.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/plugin.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/printer.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/printer.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/resolve-targets-browser.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/resolve-targets-browser.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/resolve-targets.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/resolve-targets.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/util.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/util.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/validation/option-assertions.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/validation/option-assertions.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/validation/options.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/validation/options.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/validation/plugins.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/validation/plugins.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/config/validation/removed.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/config/validation/removed.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/errors/config-error.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/errors/config-error.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/gensync-utils/async.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/gensync-utils/async.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/gensync-utils/fs.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/gensync-utils/fs.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/gensync-utils/functional.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/gensync-utils/functional.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/parse.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/parse.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/parser/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/parser/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/tools/build-external-helpers.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/tools/build-external-helpers.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transform-ast.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transform-ast.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transform-file-browser.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transform-file-browser.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transform-file.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transform-file.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transform.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transform.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/file/file.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/file/file.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/file/generate.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/file/generate.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/file/merge-map.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/file/merge-map.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/normalize-file.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/normalize-file.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/normalize-opts.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/normalize-opts.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/plugin-pass.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/plugin-pass.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/transformation/util/clone-deep.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/transformation/util/clone-deep.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/lib/vendor/import-meta-resolve.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/lib/vendor/import-meta-resolve.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/core/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/src/config/files/index-browser.ts is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/src/config/files/index.ts is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/src/config/resolve-targets-browser.ts is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/src/config/resolve-targets.ts is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/src/transform-file-browser.ts is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/core/src/transform-file.ts is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/buffer.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/buffer.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/base.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/base.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/classes.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/classes.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/deprecated.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/deprecated.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/expressions.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/expressions.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/flow.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/flow.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/jsx.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/jsx.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/methods.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/methods.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/modules.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/modules.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/statements.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/statements.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/template-literals.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/template-literals.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/types.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/types.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/generators/typescript.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/generators/typescript.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/node/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/node/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/node/parentheses.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/node/parentheses.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/node/whitespace.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/node/whitespace.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/printer.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/printer.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/source-map.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/source-map.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/lib/token-map.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/generator/lib/token-map.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/generator/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/debug.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/debug.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-compilation-targets/lib/filter-items.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/filter-items.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-compilation-targets/lib/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-compilation-targets/lib/options.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/options.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-compilation-targets/lib/pretty.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/pretty.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-compilation-targets/lib/targets.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/targets.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-compilation-targets/lib/utils.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-compilation-targets/lib/utils.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-compilation-targets/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-globals/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-globals/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-globals/data/browser-upper.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-globals/data/builtin-lower.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-globals/data/builtin-upper.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-globals/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-imports/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-imports/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-imports/lib/import-builder.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-imports/lib/import-builder.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-module-imports/lib/import-injector.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-imports/lib/import-injector.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-module-imports/lib/index.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-imports/lib/index.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-module-imports/lib/is-module.js is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-imports/lib/is-module.js.map is excluded by !**/node_modules/**, !**/*.map
  • frontend/node_modules/@babel/helper-module-imports/package.json is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-transforms/LICENSE is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-transforms/README.md is excluded by !**/node_modules/**
  • frontend/node_modules/@babel/helper-module-transforms/lib/dynamic-import.js is excluded by !**/node_modules/**
📒 Files selected for processing (20)
  • .gitignore (1 hunks)
  • README.md (1 hunks)
  • backend/.env (1 hunks)
  • backend/Dockerfile (1 hunks)
  • backend/package.json (1 hunks)
  • backend/prisma/schema.prisma (1 hunks)
  • backend/prisma/seed.ts (1 hunks)
  • backend/src/middlewares/auth.ts (1 hunks)
  • backend/src/prisma.ts (1 hunks)
  • backend/src/routes/admin.ts (1 hunks)
  • backend/src/routes/ads.ts (1 hunks)
  • backend/src/routes/auth.ts (1 hunks)
  • backend/src/routes/deeplinks.ts (1 hunks)
  • backend/src/server.ts (1 hunks)
  • backend/src/services/cache.ts (1 hunks)
  • backend/tsconfig.json (1 hunks)
  • design/DesignGuidelines.md (1 hunks)
  • docker-compose.yml (1 hunks)
  • frontend/Dockerfile (1 hunks)
  • frontend/index.html (1 hunks)
🧰 Additional context used
🪛 dotenv-linter (4.0.0)
backend/.env

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 4-4: [UnorderedKey] The NODE_ENV key should go before the PORT key

(UnorderedKey)

🔇 Additional comments (8)
frontend/index.html (1)

7-7: Inline theme script is appropriate for preventing flash.

The inline script that sets data-theme and data-orientation is correctly placed to prevent flash of unstyled content (FOUC) when the page loads. This is a valid pattern for theme initialization.

backend/tsconfig.json (1)

1-12: TypeScript configuration is solid.

The configuration appropriately targets ES2020 with CommonJS modules for the Node.js backend, enables strict type checking, and sets up correct input/output directories. The settings align well with the project structure.

.gitignore (1)

4-5: Excellent: .env files are now properly ignored.

Adding .env and .env.local to .gitignore is critical for preventing secrets from being committed to version control. This addresses the security concern with the committed backend/.env file in this PR.

Important: The existing backend/.env file in this PR should be removed from the repository and replaced with a .env.example template file.

frontend/Dockerfile (1)

1-13: Multi-stage build with nginx is well-structured.

The Dockerfile effectively uses a multi-stage build to compile the frontend in a Node environment and serve it with nginx, resulting in a minimal production image. This is a solid approach for deploying React applications.

backend/src/services/cache.ts (1)

1-5: LGTM for a simple shared in-memory TTL cache.

backend/src/server.ts (1)

16-20: /admin route is properly protected by auth middleware.

The /admin route in server.ts (line 19) is mounted to adminRouter, which applies both authMiddleware and adminOnly at the router level (lines 8-9 in backend/src/routes/admin.ts). Authentication is enforced before any admin endpoint executes.

backend/src/routes/admin.ts (1)

8-10: Middleware stack is appropriately strict for admin-only routes.
Auth + adminOnly at the router level is clean and hard to bypass.

backend/src/middlewares/auth.ts (1)

35-45: adminOnly / clientOrAdmin look fine; consider centralizing role checks (optional).
If you add more roles later, a small helper avoids duplicated branching.

- Add backend/.env to .gitignore to prevent committing secrets
- Create .env.example template with all required configuration placeholders
- Update README with environment setup instructions
- Prevents accidental exposure of DATABASE_URL, JWT_SECRET, ADMIN_INVITE_KEY
Copy link

@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: 4

🧹 Nitpick comments (5)
frontend/nginx.conf (2)

20-20: X-XSS-Protection is deprecated and can cause issues in modern browsers.

Modern browsers have removed XSS Auditor, and this header can introduce vulnerabilities in some edge cases. Consider removing it or replacing with a Content-Security-Policy header for XSS protection.

-    add_header X-XSS-Protection "1; mode=block" always;
+    # Consider adding CSP instead:
+    # add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;

37-39: 5xx errors routing to index.html may mask server issues.

While routing 404 to index.html is correct for SPA client-side routing, redirecting 5xx errors might make debugging harder. Consider keeping default error pages for server errors or adding a dedicated error page.

     # Error pages
     error_page 404 /index.html;
-    error_page 500 502 503 504 /index.html;
+    # Keep default 5xx handling or use a dedicated error page
+    # error_page 500 502 503 504 /50x.html;
backend/prisma/seed.ts (1)

72-76: createMany doesn't handle re-runs gracefully.

Unlike the upsert operations for users and clients, createMany for advertisements will fail or create duplicates when the seed is run multiple times. Consider using upsert or adding skipDuplicates: true:

   // Ads
-  await prisma.advertisement.createMany({ data: [
-    { ad_name: 'Holiday Sale', target_url: 'https://example.com/holiday', start_date: new Date(), end_date: new Date(new Date().getTime() + 1000*60*60*24*30), status: 'ACTIVE', clientId: clientA.id },
-    { ad_name: 'New Product', target_url: 'https://example.com/new', start_date: new Date(), end_date: new Date(new Date().getTime() + 1000*60*60*24*15), status: 'PAUSED', clientId: clientA.id }
-  ]});
+  await prisma.advertisement.createMany({ 
+    data: [
+      { ad_name: 'Holiday Sale', target_url: 'https://example.com/holiday', start_date: new Date(), end_date: new Date(new Date().getTime() + 1000*60*60*24*30), status: 'ACTIVE', clientId: clientA.id },
+      { ad_name: 'New Product', target_url: 'https://example.com/new', start_date: new Date(), end_date: new Date(new Date().getTime() + 1000*60*60*24*15), status: 'PAUSED', clientId: clientA.id }
+    ],
+    skipDuplicates: true,
+  });
backend/src/middlewares/auth.ts (1)

6-12: Consider defining a typed interface for req.user.

Using any for req.user loses type safety. A typed interface would provide better IntelliSense and catch errors at compile time.

+interface AuthUser {
+  id: number;
+  role: string;
+  clientId: number | null;
+}
+
 declare global {
   namespace Express {
     interface Request {
-      user?: any;
+      user?: AuthUser;
     }
   }
 }
backend/src/config.ts (1)

64-65: Consider validating PORT is a valid number.

Number(PORT) will produce NaN for invalid inputs like "abc", which could cause silent failures when binding the server.

+function parsePort(value: string | number): number {
+  const port = Number(value);
+  if (isNaN(port) || port < 1 || port > 65535) {
+    console.error(`❌ Invalid PORT value: ${value}`);
+    process.exit(1);
+  }
+  return port;
+}
+
 export const config = {
   // ...
   server: {
-    port: Number(PORT),
+    port: parsePort(PORT),
     nodeEnv: NODE_ENV,
     // ...
   },
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e605ec and dbac546.

📒 Files selected for processing (10)
  • .gitignore (1 hunks)
  • README.md (1 hunks)
  • backend/.env.example (1 hunks)
  • backend/prisma/seed.ts (1 hunks)
  • backend/src/config.ts (1 hunks)
  • backend/src/middlewares/auth.ts (1 hunks)
  • backend/src/routes/auth.ts (1 hunks)
  • backend/src/server.ts (1 hunks)
  • frontend/Dockerfile (1 hunks)
  • frontend/nginx.conf (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/Dockerfile
  • backend/src/server.ts
  • .gitignore
🧰 Additional context used
🧬 Code graph analysis (2)
backend/src/routes/auth.ts (2)
backend/src/config.ts (1)
  • config (53-70)
backend/src/middlewares/auth.ts (1)
  • authMiddleware (26-73)
backend/src/middlewares/auth.ts (1)
backend/src/config.ts (1)
  • config (53-70)
🪛 dotenv-linter (4.0.0)
backend/.env.example

[warning] 7-7: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 13-13: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 17-17: [UnorderedKey] The NODE_ENV key should go before the PORT key

(UnorderedKey)

🪛 LanguageTool
README.md

[style] ~178-~178: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...tes CLIENT users - Admin accounts can ONLY be created via: - Database seed wit...

(ADVERB_REPETITION_PREMIUM)

🪛 markdownlint-cli2 (0.18.1)
README.md

26-26: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


27-27: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


28-28: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


29-29: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


30-30: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


31-31: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


32-32: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


33-33: Unordered list indentation
Expected: 2; Actual: 5

(MD007, ul-indent)


34-34: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


35-35: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


36-36: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


59-59: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


67-67: Unordered list indentation
Expected: 0; Actual: 3

(MD007, ul-indent)


186-186: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


187-187: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


188-188: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


189-189: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


190-190: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


193-193: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


194-194: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


197-197: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)

🔇 Additional comments (13)
backend/.env.example (1)

1-26: Well-documented example configuration file.

Good practices observed:

  • Clear instructions for generating secure secrets
  • Appropriate documentation of optional vs required variables
  • Security warnings about seed credentials

The static analysis warnings about quote characters are false positives for an example file, and the key ordering suggestion is negligible.

backend/prisma/seed.ts (1)

25-47: Good implementation of environment-driven password handling.

The credential management now properly:

  • Requires explicit passwords in production (fails fast)
  • Generates secure random passwords in development
  • Logs generated passwords for local testing convenience

This addresses the previous review concerns about hardcoded credentials.

backend/src/middlewares/auth.ts (2)

14-15: Good: JWT_SECRET now uses centralized, validated configuration.

The secret is sourced from the validated config module, eliminating the insecure fallback that was flagged in the previous review.


26-72: Well-structured authentication middleware.

The implementation properly:

  • Validates Bearer token format strictly (lines 34-38)
  • Catches JWT verification errors internally and returns generic messages (lines 44-50)
  • Distinguishes auth failures (401) from server errors (500)
  • Only sets req.user after all validations pass

This addresses all concerns from the previous review.

backend/src/config.ts (1)

24-48: Solid fail-fast configuration validation.

Good practices:

  • Validates critical variables at startup
  • Logs helpful error messages without exposing sensitive values
  • Uses process.exit(1) to prevent running with invalid config
backend/src/routes/auth.ts (5)

1-13: Previous issues have been addressed.

The centralized config is now correctly used for JWT_SECRET and ADMIN_INVITE_KEY, and the public registration endpoint no longer allows ADMIN role creation.

Note: adminOnly is imported on line 6 but not used in this file. Consider removing the unused import.


15-27: LGTM!

The Zod schemas correctly enforce input validation: registerSchema no longer accepts a role field (addressing the previous security concern), and adminRegisterSchema properly requires an inviteKey.


34-73: LGTM!

The public registration endpoint correctly enforces CLIENT-only role creation. Password hashing and error handling are properly implemented.


127-151: LGTM!

The login endpoint follows security best practices: generic error messages prevent user enumeration, bcrypt.compare is timing-safe, and JWT expiry is properly configured.


153-166: LGTM!

The /me endpoint is properly protected with authMiddleware and correctly fetches the authenticated user's information.

README.md (3)

26-36: Documentation is accurate and comprehensive.

The security notes about environment variables and fail-fast configuration validation are excellent. Static analysis flagged minor list indentation issues (lines 26-36 use 3-space indent instead of 0), but this renders correctly in most Markdown parsers.


162-209: Thorough security documentation.

The security considerations section provides excellent guidance on configuration management, authentication flows, and production recommendations. The acknowledgment that localStorage tokens should be replaced with session cookies for production is particularly valuable.


211-222: LGTM!

The roadmap appropriately identifies important future enhancements (tests, CI, refresh tokens, audit logs) that align with production-readiness requirements.

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