Skip to content

Conversation

@Saahi30
Copy link
Collaborator

@Saahi30 Saahi30 commented Oct 23, 2025

📝 Description

This pull request introduces enhancements and new features to the contracts module in the backend and frontend. It includes updates to the database schema, API endpoints, and frontend components for managing contracts. These changes aim to improve the functionality, scalability, and user experience of the contracts system.

🔧 Changes Made

Backend:
Database Schema Updates:

Enhanced the contracts table with additional fields such as contract_title, contract_type, terms_and_conditions, payment_terms, deliverables, start_date, end_date, total_budget, and more.
Added new tables for contract_analytics and contract_notifications to support analytics and notifications.
API Enhancements:

Added endpoints for creating, updating, deleting, and fetching contracts.
Introduced analytics and notifications endpoints for contracts.
Implemented search functionality for contracts with filtering options.
Added support for updating contract status and marking notifications as read.
Error Handling:

Improved error handling for contract-related operations.
Frontend:
Contracts Page:

Updated the contracts page to display contract details, analytics, and notifications.
Added a modal for creating new contracts.
Enhanced the UI for managing contracts, including filtering and searching.
UI/UX Improvements:

Improved the design and layout of the contracts management interface.

✅ Checklist

  • [ ✅] I have read the contributing guidelines.

Summary by CodeRabbit

  • New Features
    • Launched comprehensive brand dashboard with real-time KPIs, campaign metrics, and creator management
    • Added AI-powered search assistant enabling intelligent query routing and navigation
    • Introduced contract management system with milestones, deliverables, and payment tracking
    • Implemented creator matching and advanced search with filtering capabilities
    • Added financial analytics and revenue dashboards for performance insights
    • Integrated notifications and timeline views for key business events

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 23, 2025

Walkthrough

This PR introduces a comprehensive brand dashboard system with AI-powered query routing, contract management, and creator matching. It adds new database tables, backend routes for brand profiles, campaigns, analytics, and contracts, plus frontend components and services to support dashboard visualization, AI-assisted search, and contract details viewing.

Changes

Cohort / File(s) Summary
Configuration & Dependencies
Backend/.env-example, Backend/requirements.txt
Added GROQ_API_KEY placeholder and Redis Cloud configuration (REDIS_HOST, REDIS_PORT, REDIS_PASSWORD). Added dependencies: groq==0.4.2 and openai==1.12.0.
Backend Models & Data Schema
Backend/app/models/models.py, Backend/sql.txt
Added ORM models (BrandProfile, CampaignMetrics, Contract, CreatorMatch) with timezone-aware timestamps. Expanded SQL schema with brand_profiles, campaign_metrics, contracts, contract_templates, contract_milestones, contract_deliverables, contract_payments, contract_comments, contract_analytics, contract_notifications tables; updated sponsorships, sponsorship_payments, user_posts, audience_insights, campaign_metrics with new columns.
Backend AI & Session Services
Backend/app/services/ai_router.py, Backend/app/services/ai_services.py, Backend/app/services/redis_client.py
Introduced AIRouter service with Groq-based LLM query processing, route catalog, and intent-to-parameter mapping. Updated ChatGroq model to moonshotai/kimi-k2-instruct with temperature 0.6 and max_completion_tokens 1024. Added Redis session state management (get_session_state, save_session_state) with SESSION_TTL and Redis Cloud configuration.
Backend Routes - Brand Dashboard
Backend/app/routes/brand_dashboard.py
New comprehensive route module with 30+ endpoints for dashboard overview, KPIs, campaigns, creator matching, analytics, applications, payments, and metrics management.
Backend Routes - AI & Contracts
Backend/app/routes/ai_query.py, Backend/app/routes/contracts.py
Added AI query routing endpoint with hybrid intent mapping and session management. Introduced full-featured contracts management API with 35+ endpoints for CRUD operations on contracts, templates, milestones, deliverables, payments, comments, analytics, and notifications.
Backend Schemas & App Setup
Backend/app/schemas/schema.py, Backend/app/main.py
Added 15+ new Pydantic schema classes for brand profiles, campaign metrics, contracts, creator matches, and dashboard responses. Registered three new routers (brand_dashboard_router, ai_query_router, contracts_router) in FastAPI app.
Frontend Services
Frontend/src/services/brandApi.ts, Frontend/src/services/aiApi.ts
Introduced BrandApiService with type-safe wrappers for all brand dashboard API endpoints. Added AIApiService with query processing, route discovery, and session management via headers.
Frontend Hooks & Context
Frontend/src/hooks/useBrandDashboard.ts, Frontend/src/context/AuthContext.tsx
Created useBrandDashboard custom hook for centralized dashboard state management and data operations. Updated AuthContext to ensure Boolean conversion for onboarding checks.
Frontend Components
Frontend/src/components/chat/BrandChatAssistant.tsx, Frontend/src/components/contracts/ContractDetailsModal.tsx, Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx, Frontend/src/components/user-nav.tsx
Added BrandChatAssistant embedded chat UI with AI response handling and session persistence. Introduced ContractDetailsModal multi-tabbed contract viewer. Updated CreatorMatchGrid key generation. Added showDashboard prop to UserNav.
Frontend Pages & Routing
Frontend/src/pages/Brand/Dashboard.tsx, Frontend/src/pages/Brand/DashboardOverview.tsx, Frontend/src/pages/Contracts.tsx, Frontend/src/App.tsx
Redesigned Dashboard with sidebar navigation and AI chat integration (renamed export to BrandDashboard). Added DashboardOverview page with KPIs, campaigns, creators, analytics sections. Replaced placeholder Contracts with full-featured contracts dashboard UI. Registered new routes /brand/dashboard/overview and /brand/contracts.
Frontend Styling & Documentation
Frontend/src/index.css, Frontend/README-INTEGRATION.md
Added Orbitron font import. Added integration documentation describing completed frontend-backend API coverage, custom hooks, components, and testing instructions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant BrandChatAssistant
    participant AIApiService
    participant AIRouterBackend
    participant GroqLLM
    participant RedisSession

    User->>BrandChatAssistant: Enter query
    BrandChatAssistant->>AIApiService: queryAI(query, brandId, sessionId?)
    
    alt Has Session ID
        AIApiService->>AIApiService: Add X-Session-ID header
    else No Session
        AIApiService->>AIApiService: Generate new session ID
    end
    
    AIApiService->>AIRouterBackend: POST /api/ai/query
    
    AIRouterBackend->>RedisSession: get_session_state(session_id)
    RedisSession-->>AIRouterBackend: previous state (if any)
    
    AIRouterBackend->>GroqLLM: Send system prompt + user query
    GroqLLM-->>AIRouterBackend: LLM response (JSON intent)
    
    AIRouterBackend->>AIRouterBackend: Parse intent, map to route & parameters
    AIRouterBackend->>AIRouterBackend: Invoke mapped API endpoint
    AIRouterBackend->>RedisSession: save_session_state(session_id, context)
    
    AIRouterBackend-->>AIApiService: AIQueryResponse (intent, route, explanation, sessionId)
    AIApiService-->>BrandChatAssistant: Response with session context
    BrandChatAssistant->>BrandChatAssistant: Update sessionId for next query
    BrandChatAssistant->>User: Display AI response & follow-up
Loading
sequenceDiagram
    participant BrandDashboard
    participant useBrandDashboard
    participant BrandApiService
    participant Backend
    
    rect rgb(200, 220, 240)
    Note over BrandDashboard,Backend: Initial Dashboard Load
    BrandDashboard->>BrandDashboard: Mount
    BrandDashboard->>useBrandDashboard: useEffect call refreshData()
    useBrandDashboard->>BrandApiService: getDashboardOverview(brandId)
    useBrandDashboard->>BrandApiService: getBrandCampaigns(brandId)
    useBrandDashboard->>BrandApiService: getCreatorMatches(brandId)
    useBrandDashboard->>BrandApiService: getBrandApplications(brandId)
    BrandApiService->>Backend: Parallel API calls
    Backend-->>BrandApiService: Aggregated dashboard data
    BrandApiService-->>useBrandDashboard: Update hook state
    useBrandDashboard-->>BrandDashboard: Return loaded data
    BrandDashboard->>BrandDashboard: Render dashboard UI with KPIs & metrics
    end
    
    rect rgb(240, 200, 220)
    Note over BrandDashboard,Backend: AI Search Flow
    BrandDashboard->>BrandDashboard: User triggers AI search
    BrandDashboard->>BrandChatAssistant: Open with initialQuery
    BrandChatAssistant->>useBrandDashboard: queryAI(query)
    useBrandDashboard->>BrandApiService: aiApi.queryAI(query, brandId)
    BrandApiService-->>useBrandDashboard: AI response + suggestions
    useBrandDashboard-->>BrandChatAssistant: Update AI results
    BrandChatAssistant->>BrandDashboard: Display AI response in chat
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Rationale: Large heterogeneous PR spanning 30+ files across frontend and backend with multiple new features (brand dashboard, contracts management, AI routing, session management). Includes dense business logic (AI query orchestration with Groq LLM, hybrid intent mapping, comprehensive CRUD operations), significant database schema changes, and multiple React components with state management. High variety of edits (new models, routes, services, components, hooks, SQL tables) requiring separate reasoning for each domain.

Possibly related PRs

Suggested labels

enhancement, backend, frontend, feature

Suggested reviewers

  • chandansgowda

Poem

🐇 A brand new dashboard blooms with grace,
AI queries find their perfect place,
Sessions dance in Redis bright,
Contracts flow, creators take flight,
From intent to action, all feels right!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Feat: Enhance Contracts Module: Database, API, and Frontend Updates" refers to a genuine and substantial part of the changeset. The PR includes significant contracts-related additions across the stack: a comprehensive contracts API module (Backend/app/routes/contracts.py), database schema extensions for contracts and related tables (in sql.txt), Pydantic schemas for contract operations, a contracts UI dashboard (Frontend/src/pages/Contracts.tsx), and a contract details modal component. The title accurately describes database, API, and frontend updates specific to the contracts domain. While the PR also introduces brand dashboard and AI query modules, the title's focus on contracts as the primary enhancement is valid and does not misrepresent the changeset—it simply emphasizes one key module rather than attempting to enumerate all changes.
Docstring Coverage ✅ Passed Docstring coverage is 88.04% which is sufficient. The required threshold is 80.00%.
✨ 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
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 38

🧹 Nitpick comments (51)
Backend/app/models/models.py (2)

20-21: Consider centralizing the generate_uuid function.

The generate_uuid() function is duplicated across multiple files (models.py, chat.py, post.py). Consider extracting it to a shared utility module to follow the DRY principle.

For example, create a utility module:

# app/utils/id_generator.py
import uuid

def generate_uuid():
    return str(uuid.uuid4())

Then import it in models:

+from app.utils.id_generator import generate_uuid
 from app.db.db import Base
-import uuid
-
-
-def generate_uuid():
-    return str(uuid.uuid4())

216-216: Consider using enums for status fields.

The status fields in Contract (line 216) and other models use string literals. Consider using Python enums for type safety and preventing invalid values.

Example for Contract status:

from enum import Enum

class ContractStatus(str, Enum):
    DRAFT = "draft"
    SIGNED = "signed"
    COMPLETED = "completed"
    CANCELLED = "cancelled"

# Then in the model:
status = Column(String, default=ContractStatus.DRAFT)

Also applies to: 234-234

Frontend/src/App.tsx (1)

72-81: Unify contracts route to one canonical path.

You now have both /brand/contracts and /dashboard/contracts. Prefer one and redirect the other to avoid duplicate screens and broken deep links.

Apply this change outside the shown range to redirect the legacy route:

- import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
+ import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";

Replace the legacy route:

- <Route
-   path="/dashboard/contracts"
-   element={
-     <ProtectedRoute>
-       <Contracts />
-     </ProtectedRoute>
-   }
- />
+ <Route path="/dashboard/contracts" element={<Navigate to="/brand/contracts" replace />} />
Frontend/src/hooks/useBrandDashboard.ts (2)

20-21: Type aiResponse to the API contract.

Use the exported AIQueryResponse type to avoid any.

- import { aiApi } from '../services/aiApi';
+ import { aiApi, AIQueryResponse } from '../services/aiApi';
...
- const [aiResponse, setAiResponse] = useState<any>(null);
+ const [aiResponse, setAiResponse] = useState<AIQueryResponse | null>(null);

29-37: Loading state doesn’t cover all initial fetches.

setLoading is toggled only in loadDashboardOverview, so UI may show loaded while other requests are pending.

Move loading control to the aggregate loader:

-  try {
-    setLoading(true);
-    const overview = await brandApi.getDashboardOverview(brandId);
-    setDashboardOverview(overview);
-  } catch (err) {
+  try {
+    const overview = await brandApi.getDashboardOverview(brandId);
+    setDashboardOverview(overview);
   } catch (err) {
     setError(err instanceof Error ? err.message : 'Failed to load dashboard overview');
-  } finally {
-    setLoading(false);
-  }
+  }

And in the effect:

  useEffect(() => {
    if (brandId) {
-     Promise.all([
+     setLoading(true);
+     setError(null);
+     Promise.all([
        loadDashboardOverview(),
        loadBrandProfile(),
        loadCampaigns(),
        loadCreatorMatches(),
        loadApplications(),
        loadPayments(),
-     ]).catch(err => {
-       console.error('Error loading dashboard data:', err);
-     });
+     ])
+     .catch(err => setError(err instanceof Error ? err.message : 'Failed to load dashboard'))
+     .finally(() => setLoading(false));
    }
  }, [brandId, loadDashboardOverview, loadBrandProfile, loadCampaigns, loadCreatorMatches, loadApplications, loadPayments]);

Also applies to: 224-239

Backend/app/services/redis_client.py (1)

19-25: Harden session helpers.

Add basic error handling to avoid crashes on Redis outages.

 async def get_session_state(session_id: str):
-    state = await redis_client.get(f"session:{session_id}")
-    return json.loads(state) if state else {}
+    try:
+        state = await redis_client.get(f"session:{session_id}")
+        return json.loads(state) if state else {}
+    except Exception:
+        return {}
 
 async def save_session_state(session_id: str, state: dict):
-    await redis_client.set(f"session:{session_id}", json.dumps(state), ex=SESSION_TTL)
+    try:
+        await redis_client.set(f"session:{session_id}", json.dumps(state), ex=SESSION_TTL)
+    except Exception:
+        pass
Frontend/src/services/aiApi.ts (1)

4-5: Don’t hard-code API base URL.

Use env vars so dev/prod work without code changes.

-const AI_API_BASE_URL = 'http://localhost:8000/api/ai';
+const AI_API_BASE_URL =
+  (import.meta as any)?.env?.VITE_AI_API_BASE_URL ||
+  `${window.location.origin}/api/ai`;
Frontend/src/pages/Contracts.tsx (3)

1-21: Tighten TypeScript types and drop unused imports.

  • Add a Contract/Payment interface and type state accordingly.
  • Remove unused imports: useEffect, Filter, Calendar, Users, AlertCircle.
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
-import { 
-  Plus, 
-  Search, 
-  Filter, 
-  Calendar, 
-  DollarSign, 
-  Users, 
-  FileText, 
-  CheckCircle, 
-  Clock, 
-  AlertCircle,
-  TrendingUp,
-  Eye,
-  Edit,
-  MoreVertical,
-  Download,
-  Upload
-} from 'lucide-react';
+import { Plus, Search, DollarSign, FileText, CheckCircle, Clock, TrendingUp, Eye, Edit, MoreVertical, Download, Upload } from 'lucide-react';
...
+type Payment = { amount: number; status: 'paid' | 'pending'; date: string };
+type Contract = {
+  id: string; title: string; creator: string; brand: string;
+  status: 'active'|'pending'|'draft'|'completed';
+  type: 'one-time'|'ongoing'|'performance-based';
+  budget: number; startDate: string; endDate: string; progress: number;
+  milestones: number; completedMilestones: number;
+  deliverables: number; completedDeliverables: number;
+  payments: Payment[];
+};
...
-const [contracts, setContracts] = useState(mockContracts);
+const [contracts, setContracts] = useState<Contract[]>(mockContracts);
-const [selectedContract, setSelectedContract] = useState(null);
+const [selectedContract, setSelectedContract] = useState<Contract | null>(null);

Also applies to: 89-96


356-375: Prevent card click when pressing inner action buttons.

Stop propagation on “View/Edit/More” so they don’t also open the details modal.

-              <button style={{ ... }}>
+              <button onClick={(e) => { e.stopPropagation(); /* open details */ }} style={{ ... }}>
                 <Eye size={14} />
                 View
               </button>
-              <button style={{ ... }}>
+              <button onClick={(e) => e.stopPropagation()} style={{ ... }}>
                 <Edit size={14} />
                 Edit
               </button>
-              <button style={{ ... }}>
+              <button onClick={(e) => e.stopPropagation()} style={{ ... }}>
                 <MoreVertical size={14} />
               </button>

Also applies to: 456-499


251-257: Revenue metric label vs logic mismatch.

totalRevenue sums paid payments across all contracts but the label says “From completed contracts”.

Either:

  • Change the label to “From paid invoices”, or
  • Filter to completed contracts:
- totalRevenue: contracts.reduce((sum, c) => {
+ totalRevenue: contracts
+   .filter(c => c.status === 'completed')
+   .reduce((sum, c) => {
      const paidPayments = c.payments.filter(p => p.status === 'paid');
      return sum + paidPayments.reduce((pSum, p) => pSum + p.amount, 0);
-   }, 0)
+   }, 0)

Also applies to: 101-107

Frontend/src/components/chat/BrandChatAssistant.tsx (1)

208-221: Render result JSON with preserved formatting.

Current JSON.stringify collapses to one line. Use pre-wrap for readability.

-  <strong>Result:</strong> {JSON.stringify(msg.result, null, 2)}
+  <strong>Result:</strong>
+  <pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
+    {JSON.stringify(msg.result, null, 2)}
+  </pre>
Frontend/src/pages/Brand/DashboardOverview.tsx (2)

120-169: Add AbortController to prevent state updates after unmount.

Protects against race conditions and memory leaks during rapid nav.

useEffect(() => {
-  const fetchDashboardData = async () => {
+  const controller = new AbortController();
+  const fetchDashboardData = async () => {
     try {
@@
-    fetchDashboardData();
-  }, [brandId]);
+    fetchDashboardData();
+    return () => controller.abort();
+  }, [brandId]);

Pass { signal: controller.signal } to each fetch.


101-118: Remove unused helpers.

getStatusColor/getPerformanceColor aren’t used; delete to reduce noise.

Backend/app/routes/ai_query.py (3)

9-11: Avoid configuring root logging in a library/router module.

logging.basicConfig here can clobber app-level logging. Remove and rely on app config.

-# Setup logging
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+# Setup logger
+logger = logging.getLogger(__name__)

64-70: Downgrade verbose param logging to debug and avoid PII in production logs.

Dumping all params (brand_id, user_id) at info can leak identifiers.

-logger.info(f"Intent: {intent}")
-logger.info(f"Params: {params}")
-logger.info(f"Params type: {type(params)}")
-for key, value in params.items():
-    logger.info(f"  {key}: {value} (type: {type(value)})")
+logger.debug("Intent: %s; params keys: %s", intent, list(params.keys()))

138-141: Use logger.exception and chain exceptions; avoid blind excepts.

Improve traceability and lint compliance.

-except Exception as api_exc:
-    logger.error(f"API call failed for intent '{intent}': {api_exc}")
-    api_error = str(api_exc)
+except Exception as api_exc:
+    logger.exception("API call failed for intent '%s'", intent)
+    api_error = str(api_exc)
@@
-except Exception as e:
-    logger.error(f"Error processing AI query: {e}")
-    raise HTTPException(status_code=500, detail="Failed to process AI query")
+except Exception as e:
+    logger.exception("Error processing AI query")
+    raise HTTPException(status_code=500, detail="Failed to process AI query") from e
@@
-except Exception as e:
-    logger.error(f"Error fetching available routes: {e}")
-    raise HTTPException(status_code=500, detail="Failed to fetch routes")
+except Exception as e:
+    logger.exception("Error fetching available routes")
+    raise HTTPException(status_code=500, detail="Failed to fetch routes") from e
@@
-except Exception as e:
-    logger.error(f"Error fetching route info: {e}")
-    raise HTTPException(status_code=500, detail="Failed to fetch route info")
+except Exception as e:
+    logger.exception("Error fetching route info")
+    raise HTTPException(status_code=500, detail="Failed to fetch route info") from e
@@
-except Exception as e:
-    logger.error(f"Error in test AI query: {e}")
+except Exception as e:
+    logger.exception("Error in test AI query")

Also applies to: 183-187, 200-202, 218-222, 238-239

Frontend/src/components/contracts/ContractDetailsModal.tsx (3)

22-42: Contract interface diverges from backend Contract model; confirm mapping.

Backend Contract lacks title/type/budget/dates in the provided model; ensure the consumer composes this view-model from API responses before passing in.

I can add a ContractViewModel mapper to adapt backend fields (including sponsorship/title, payments, deliverables).


535-559: Add dialog semantics for accessibility.

Mark modal with role="dialog" and aria-modal, and label it for screen readers.

-<div style={{ 
+<div
+  role="dialog"
+  aria-modal="true"
+  aria-labelledby={`contract-modal-title-${contract.id}`}
+  style={{ 
@@
-  <div style={{
+  <div
+    role="document"
+    style={{
@@
-  <h2 style={{ fontSize: '24px', fontWeight: '700', marginBottom: '4px' }}>{contract.title}</h2>
+  <h2 id={`contract-modal-title-${contract.id}`} style={{ fontSize: '24px', fontWeight: '700', marginBottom: '4px' }}>
+    {contract.title}
+  </h2>

560-616: Optional: support Esc/overlay click to close.

Improves UX parity with common modal behavior.

Frontend/src/pages/Brand/Dashboard.tsx (1)

465-469: Use onKeyDown instead of onKeyPress (deprecated).

React deprecates onKeyPress; switch to onKeyDown for Enter handling.

-onKeyPress={(e) => {
+onKeyDown={(e) => {
   if (e.key === 'Enter' && searchQuery.trim()) {
     handleAISearch();
   }
 }}
Backend/app/services/ai_router.py (5)

131-138: Use explicit Optional typing for brand_id.

PEP 484: avoid implicit Optional. Also helps tools.

-    async def process_query(self, query: str, brand_id: str = None) -> Dict[str, Any]:
+    async def process_query(self, query: str, brand_id: Optional[str] = None) -> Dict[str, Any]:

167-169: Harden exception handling (log trace, chain cause).

Use logging.exception and raise from to preserve stack traces.

-        except Exception as e:
-            logger.error(f"Error processing query with AI Router: {e}")
-            raise HTTPException(status_code=500, detail="AI processing error")
+        except Exception as e:
+            logger.exception("Error processing query with AI Router")
+            raise HTTPException(status_code=500, detail="AI processing error") from e

194-197: Use timezone-aware ISO timestamps.

Naive datetime can confuse clients. Prefer UTC ISO 8601.

-        response["timestamp"] = str(datetime.now())
+        response["timestamp"] = datetime.utcnow().isoformat() + "Z"

200-223: Regex/json cleaning is brittle for nested JSON.

Your pattern may truncate nested objects/arrays. Consider a stricter extractor or require code-block JSON only.


342-343: Avoid failing import-time singletons.

If GROQ_API_KEY is missing in certain envs (e.g., tests), module import will crash.

-# Global instance
-ai_router = AIRouter() 
+# Global instance (lazy)
+ai_router: Optional[AIRouter] = None
+try:
+    ai_router = AIRouter()
+except Exception as e:
+    logger.warning("AIRouter not initialized at import: %s", e)
Backend/sql.txt (3)

478-492: Add CHECKs or ENUMs for status fields.

Codify allowed values (contracts, milestones, deliverables, payments) to prevent drift like 'active' vs commented set.


1-10: Ensure pgcrypto extension is enabled.

gen_random_uuid() requires pgcrypto (or pgcrypto-like). Add a guard.

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Also applies to: 48-51, 71-74


539-566: Minor nomenclature in comments (cosmetic).

Capitalize platform/payment names: YouTube, TikTok, Twitter, PayPal.

Frontend/src/services/brandApi.ts (2)

4-4: Don’t hardcode API base URL.

Use env to support staging/prod and SSR.

-const API_BASE_URL = 'http://localhost:8000/api/brand';
+const API_BASE_URL =
+  (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_API_BASE_URL) ||
+  import.meta?.env?.VITE_API_BASE_URL ||
+  'http://localhost:8000/api/brand';

165-178: Preserve zero values in filters.

Truthy check drops 0; use undefined check.

-    if (filters?.min_engagement) params.append('min_engagement', filters.min_engagement.toString());
+    if (filters?.min_engagement !== undefined) {
+      params.append('min_engagement', String(filters.min_engagement));
+    }
Backend/app/schemas/schema.py (2)

2-2: Import Any for Dict payloads.

Some fields represent arbitrary JSON objects; add Any.

-from typing import Optional, Dict, List
+from typing import Optional, Dict, List, Any

151-157: Type recent_activity precisely.

Use List[Dict[str, Any]] for clarity and better validation.

-    recent_activity: list
+    recent_activity: List[Dict[str, Any]]
Backend/app/routes/contracts.py (5)

235-236: Avoid broad Exception; chain causes and log traces.

Standardize to log, then raise with cause.

-    except Exception as e:
-        raise HTTPException(status_code=500, detail=f"...: {str(e)}")
+    except Exception as e:
+        # logger.exception("Context message")  # add if a logger is available
+        raise HTTPException(status_code=500, detail="Internal server error") from e

Also applies to: 262-264, 278-280, 298-300, 314-316, 335-337, 362-364, 378-380, 399-401, 410-412, 447-448, 467-469, 478-480, 498-500, 514-516, 536-538, 547-549, 565-569, 583-585, 605-607, 616-618, 632-634, 647-649, 665-666, 684-686, 700-702, 739-741, 773-774


650-657: Validate analytics payload against schema/table columns.

Accepting arbitrary Dict risks 400s from unexpected keys. Define an AnalyticsCreate model mirroring table columns.

-class AnalyticsResponse(BaseModel):
+class AnalyticsResponse(BaseModel):
     ...
 
+class AnalyticsCreate(BaseModel):
+    performance_metrics: Optional[Dict[str, Any]] = None
+    engagement_data: Optional[Dict[str, Any]] = None
+    revenue_generated: Optional[float] = 0
+    roi_percentage: Optional[float] = 0
+    cost_per_engagement: Optional[float] = 0
+    cost_per_click: Optional[float] = 0
...
-async def create_contract_analytics(contract_id: str, analytics_data: Dict[str, Any]):
+async def create_contract_analytics(contract_id: str, analytics_data: AnalyticsCreate):
...
-            "contract_id": contract_id,
-            **analytics_data
+            "contract_id": contract_id,
+            **analytics_data.dict(exclude_unset=True)

707-737: Robustness in stats computation.

Handle empty results; avoid None.

-        contracts = result.data
+        contracts = result.data or []

746-773: Avoid shadowing built-ins and use safer search param name.

query param named query is fine, but local var search_query collides semantically. Also ensure ilike on empty data handled.

-async def search_contracts(
-    query: str = Query(..., description="Search term"),
+async def search_contracts(
+    q: str = Query(..., description="Search term"),
...
-        result = search_query.ilike("contract_title", f"%{query}%").limit(limit).execute()
+        result = search_query.ilike("contract_title", f"%{q}%").limit(limit).execute()

1-9: Remove unused imports.

httpx and Depends aren’t used.

-from fastapi import APIRouter, HTTPException, Depends, Query
+from fastapi import APIRouter, HTTPException, Query
-import httpx
Backend/app/routes/brand_dashboard.py (14)

67-75: Improve DB error handling and diagnostics.

Surface Supabase errors, use logger.exception, and preserve traceback via raise ... from e.

 def safe_supabase_query(query_func, error_message: str = "Database operation failed"):
     """Safely execute Supabase queries with proper error handling"""
-    try:
-        result = query_func()
-        return result.data if result.data else []
-    except Exception as e:
-        logger.error(f"Supabase error in {error_message}: {e}")
-        raise HTTPException(status_code=500, detail=error_message)
+    try:
+        result = query_func()
+    except Exception as e:
+        logger.exception(f"Supabase error in {error_message}")
+        raise HTTPException(status_code=500, detail=error_message) from e
+    # Supabase responses may carry .error alongside .data
+    resp_error = getattr(result, "error", None)
+    if resp_error:
+        logger.error(f"Supabase responded with error in {error_message}: {resp_error}")
+        raise HTTPException(status_code=500, detail=error_message)
+    return result.data or []

28-36: Fail fast on missing Supabase config; avoid configuring logging here.

  • Validate SUPABASE_URL/KEY at import to prevent None from reaching create_client.
  • Avoid logging.basicConfig in libraries; set logging in app entrypoint.
 # Load environment variables
 load_dotenv()
 url: str = os.getenv("SUPABASE_URL")
 key: str = os.getenv("SUPABASE_KEY")
-supabase: Client = create_client(url, key)
+if not url or not key:
+    raise RuntimeError("Supabase configuration missing: SUPABASE_URL/SUPABASE_KEY")
+supabase: Client = create_client(url, key)
@@
-logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)

Confirm logging is configured in Backend/app/main.py; otherwise move it there.


113-116: Remove unused brand profile fetch.

Dead call + unused variable; saves a DB roundtrip.

-        # Get brand's profile
-        profile_result = supabase.table("brand_profiles").select("*").eq("user_id", brand_id).execute()
-        profile = profile_result.data[0] if profile_result.data else None

367-380: Filter pending applications in SQL; avoid scanning all.

Push brand campaign IDs into the query.

-        applications = safe_supabase_query(
-            lambda: supabase.table("sponsorship_applications").select("*").eq("status", "pending").execute(),
-            "Failed to fetch applications"
-        )
-        
-        # Filter applications for this brand's campaigns
-        brand_campaigns = safe_supabase_query(
+        brand_campaigns = safe_supabase_query(
             lambda: supabase.table("sponsorships").select("id").eq("brand_id", brand_id).execute(),
             "Failed to fetch brand campaigns"
         )
         
         brand_campaign_ids = [campaign["id"] for campaign in brand_campaigns]
-        pending_applications = [app for app in applications if app.get("sponsorship_id") in brand_campaign_ids]
+        pending_applications = safe_supabase_query(
+            lambda: supabase.table("sponsorship_applications")
+                .select("*")
+                .eq("status", "pending")
+                .in_("sponsorship_id", brand_campaign_ids)
+                .execute(),
+            "Failed to fetch applications"
+        )

762-785: Schema mismatch risk: extra fields not in CreatorMatchResponse.

You add creator_name/creator_role but response_model lacks them; FastAPI will drop extras. If frontend expects these, extend schema accordingly or stop injecting them.

Would you like me to patch Backend/app/schemas/schema.py to include:

  • creator_name: Optional[str] = None
  • creator_role: Optional[str] = None

813-829: Use provided filters or remove unused params.

industry/location are accepted but unused. Implement at least location via audience_insights.top_markets.

         for creator in creators:
             creator_insights = insights_lookup.get(creator["id"])
             
             # Apply filters
             if min_engagement and creator_insights:
                 if creator_insights.get("engagement_rate", 0) < min_engagement:
                     continue
+            if location and creator_insights:
+                top_markets = creator_insights.get("top_markets", {})
+                if isinstance(top_markets, dict) and location not in top_markets:
+                    continue

877-891: Analytics correctness/perf: pick latest metrics per campaign only.

Building a single metrics_lookup from all records collapses history arbitrarily. Query latest per campaign (order/limit) or aggregate by recorded_at in SQL. Also filter metrics by the brand’s campaign IDs to avoid scanning entire table.

If you want, I can provide a batched query pattern using Supabase RPC or a single in_ with order(..., desc=True).limit(1) per campaign.


922-947: Naming: these are payouts (spend), not revenue.

Completed sponsorship_payments represent brand outflows. Consider renaming fields/route to “spend” to avoid confusion with top‑line revenue metrics.


1031-1073: N+1 queries when enriching applications.

Batch fetch creators and campaigns using in_ to cut roundtrips.

-        enhanced_applications = []
-        for application in applications:
-            # Get creator details
-            creator_result = supabase.table("users").select("*").eq("id", application["creator_id"]).execute()
-            creator = creator_result.data[0] if creator_result.data else None
-            # Get campaign details
-            campaign_result = supabase.table("sponsorships").select("*").eq("id", application["sponsorship_id"]).execute()
-            campaign = campaign_result.data[0] if campaign_result.data else None
-            enhanced_application = {**application,"creator": creator,"campaign": campaign}
-            enhanced_applications.append(enhanced_application)
+        creator_ids = [a["creator_id"] for a in applications]
+        creators = safe_supabase_query(lambda: supabase.table("users").select("*").in_("id", creator_ids).execute(), "Failed to fetch creators")
+        creators_by_id = {c["id"]: c for c in creators}
+        campaigns_by_id = {c["id"]: c for c in campaigns}
+        enhanced_applications = [
+            {**a, "creator": creators_by_id.get(a["creator_id"]), "campaign": campaigns_by_id.get(a["sponsorship_id"])}
+            for a in applications
+        ]

1214-1246: N+1 in payments enrichment.

Batch creators/campaigns via in_ similar to applications to reduce latency and load.


1325-1357: Analytics robustness: parse timestamps safely.

Slicing [:7] assumes ISO strings. Parse to date for reliability.

-                month = payment["transaction_date"][:7] if payment["transaction_date"] else "unknown"
+                ts = payment.get("transaction_date")
+                if ts:
+                    try:
+                        dt = datetime.fromisoformat(ts.replace("Z","+00:00")) if isinstance(ts, str) else ts
+                        month = dt.strftime("%Y-%m")
+                    except Exception:
+                        month = "unknown"
+                else:
+                    month = "unknown"

1-9: Remove unused SQLAlchemy imports (and related dead code).

AsyncSession, select, AsyncSessionLocal aren’t used in this module.


76-93: Rate limiting helper is unused.

Either wire it into mutating endpoints or remove to avoid dead state in process memory. Prefer Redis‑backed rate limiting as noted in PR summary.


161-276: Async endpoints call sync client; event‑loop blocking.

Supabase Python client is sync. Wrap calls in run_in_threadpool or switch to an async client to prevent request latency under load. I can provide a small safe_supabase_query_async helper and migrate hot paths first.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3be437 and 80970cc.

⛔ Files ignored due to path filters (1)
  • Frontend/public/aossielogo.png is excluded by !**/*.png
📒 Files selected for processing (26)
  • Backend/.env-example (1 hunks)
  • Backend/app/main.py (2 hunks)
  • Backend/app/models/models.py (2 hunks)
  • Backend/app/routes/ai_query.py (1 hunks)
  • Backend/app/routes/brand_dashboard.py (1 hunks)
  • Backend/app/routes/contracts.py (1 hunks)
  • Backend/app/schemas/schema.py (2 hunks)
  • Backend/app/services/ai_router.py (1 hunks)
  • Backend/app/services/ai_services.py (1 hunks)
  • Backend/app/services/redis_client.py (1 hunks)
  • Backend/requirements.txt (1 hunks)
  • Backend/sql.txt (1 hunks)
  • Frontend/README-INTEGRATION.md (1 hunks)
  • Frontend/src/App.tsx (2 hunks)
  • Frontend/src/components/chat/BrandChatAssistant.tsx (1 hunks)
  • Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx (1 hunks)
  • Frontend/src/components/contracts/ContractDetailsModal.tsx (1 hunks)
  • Frontend/src/components/user-nav.tsx (2 hunks)
  • Frontend/src/context/AuthContext.tsx (1 hunks)
  • Frontend/src/hooks/useBrandDashboard.ts (1 hunks)
  • Frontend/src/index.css (1 hunks)
  • Frontend/src/pages/Brand/Dashboard.tsx (1 hunks)
  • Frontend/src/pages/Brand/DashboardOverview.tsx (1 hunks)
  • Frontend/src/pages/Contracts.tsx (1 hunks)
  • Frontend/src/services/aiApi.ts (1 hunks)
  • Frontend/src/services/brandApi.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
Frontend/src/hooks/useBrandDashboard.ts (3)
Frontend/src/context/AuthContext.tsx (1)
  • useAuth (216-222)
Frontend/src/services/brandApi.ts (14)
  • DashboardOverview (7-13)
  • BrandProfile (15-24)
  • Campaign (26-36)
  • CreatorMatch (38-44)
  • Application (46-56)
  • Payment (58-68)
  • brandApi (246-246)
  • createCampaign (132-144)
  • updateCampaign (146-151)
  • deleteCampaign (153-157)
  • updateApplicationStatus (202-212)
  • searchCreators (164-178)
  • getCampaignPerformance (185-187)
  • getRevenueAnalytics (189-191)
Frontend/src/services/aiApi.ts (2)
  • queryAI (57-83)
  • aiApi (102-102)
Frontend/src/App.tsx (1)
Frontend/src/services/brandApi.ts (1)
  • DashboardOverview (7-13)
Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx (1)
Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx (1)
  • CreatorMatchCard (65-130)
Frontend/src/components/contracts/ContractDetailsModal.tsx (1)
Backend/app/models/models.py (1)
  • Contract (208-224)
Frontend/src/pages/Brand/Dashboard.tsx (1)
Frontend/src/hooks/useBrandDashboard.ts (1)
  • useBrandDashboard (6-288)
Frontend/src/pages/Brand/DashboardOverview.tsx (1)
Frontend/src/services/brandApi.ts (1)
  • DashboardOverview (7-13)
Backend/app/models/models.py (2)
Backend/app/models/chat.py (1)
  • generate_uuid (9-10)
Backend/app/routes/post.py (1)
  • generate_uuid (31-32)
Backend/app/services/ai_router.py (1)
Backend/app/routes/ai_query.py (1)
  • get_route_info (205-222)
Backend/app/routes/brand_dashboard.py (2)
Backend/app/models/models.py (5)
  • User (25-53)
  • Sponsorship (76-92)
  • CampaignMetrics (189-204)
  • Contract (208-224)
  • SponsorshipApplication (114-128)
Backend/app/schemas/schema.py (20)
  • BrandProfileCreate (61-67)
  • BrandProfileUpdate (69-74)
  • BrandProfileResponse (76-87)
  • CampaignMetricsCreate (91-97)
  • CampaignMetricsResponse (99-110)
  • ContractCreate (114-119)
  • ContractUpdate (121-123)
  • ContractResponse (125-135)
  • CreatorMatchResponse (139-147)
  • DashboardOverviewResponse (151-156)
  • CampaignAnalyticsResponse (158-166)
  • CreatorMatchAnalyticsResponse (168-174)
  • SponsorshipApplicationResponse (182-194)
  • ApplicationUpdateRequest (196-198)
  • ApplicationSummaryResponse (200-206)
  • PaymentResponse (210-222)
  • PaymentStatusUpdate (224-225)
  • PaymentAnalyticsResponse (227-233)
  • CampaignMetricsUpdate (237-242)
  • SponsorshipCreate (21-27)
Backend/app/routes/ai_query.py (3)
Backend/app/services/redis_client.py (2)
  • get_session_state (19-21)
  • save_session_state (23-24)
Backend/app/services/ai_router.py (3)
  • process_query (131-169)
  • list_available_routes (338-340)
  • get_route_info (334-336)
Backend/app/routes/brand_dashboard.py (9)
  • search_creators (792-834)
  • get_dashboard_overview (99-159)
  • get_creator_matches (763-789)
  • get_brand_profile (590-606)
  • get_brand_campaigns (634-646)
  • get_creator_profile (837-871)
  • get_campaign_performance (878-920)
  • get_revenue_analytics (923-951)
  • get_brand_contracts (958-968)
Backend/app/routes/contracts.py (1)
Frontend/src/utils/supabase.tsx (1)
  • supabase (11-11)
🪛 LanguageTool
Backend/sql.txt

[uncategorized] ~539-~539: The official name of this popular video platform is spelled with a capital “T”.
Context: ...latform TEXT NOT NULL, -- 'instagram', 'youtube', 'tiktok', 'twitter', 'facebook' r...

(YOUTUBE)


[uncategorized] ~539-~539: Did you mean “TikTok” (= social networking app)?
Context: ...T NOT NULL, -- 'instagram', 'youtube', 'tiktok', 'twitter', 'facebook' requirement...

(TIK_TOK)


[uncategorized] ~539-~539: Did you mean “Twitter” (= microblogging service, capitalized)?
Context: ..., -- 'instagram', 'youtube', 'tiktok', 'twitter', 'facebook' requirements JSONB, ...

(TWITTER)


[uncategorized] ~565-~565: The official name of this payment provider is spelled with two capital “P”.
Context: ...yment_method TEXT, -- 'bank_transfer', 'paypal', 'stripe', 'escrow' transaction_id...

(PAYPAL)

🪛 Ruff (0.14.1)
Backend/app/services/ai_router.py

22-22: Avoid specifying long messages outside the exception class

(TRY003)


131-131: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


165-165: Consider moving this statement to an else block

(TRY300)


167-167: Do not catch blind exception: Exception

(BLE001)


168-168: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


169-169: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

Backend/app/routes/brand_dashboard.py

71-71: Consider moving this statement to an else block

(TRY300)


72-72: Do not catch blind exception: Exception

(BLE001)


73-73: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


74-74: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


79-79: Unused function argument: window_seconds

(ARG001)


115-115: Local variable profile is assigned to but never used

Remove assignment to unused variable profile

(F841)


157-157: Do not catch blind exception: Exception

(BLE001)


158-158: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


159-159: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


177-177: Local variable total_campaigns is assigned to but never used

Remove assignment to unused variable total_campaigns

(F841)


182-182: Local variable total_reach is assigned to but never used

Remove assignment to unused variable total_reach

(F841)


280-280: Do not catch blind exception: Exception

(BLE001)


281-281: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


282-282: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


304-304: Function definition does not bind loop variable campaign

(B023)


351-351: Do not catch blind exception: Exception

(BLE001)


352-352: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


353-353: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


397-397: Function definition does not bind loop variable campaign

(B023)


452-454: Consider moving this statement to an else block

(TRY300)


458-458: Do not catch blind exception: Exception

(BLE001)


459-459: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


460-460: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


526-526: Do not catch blind exception: Exception

(BLE001)


527-527: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


528-528: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


552-552: Do not catch blind exception: Exception

(BLE001)


553-553: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


554-554: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


583-583: Abstract raise to an inner function

(TRY301)


585-585: Do not catch blind exception: Exception

(BLE001)


586-586: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


587-587: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


600-600: Abstract raise to an inner function

(TRY301)


604-604: Do not catch blind exception: Exception

(BLE001)


605-605: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


606-606: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


621-621: Abstract raise to an inner function

(TRY301)


625-625: Do not catch blind exception: Exception

(BLE001)


626-626: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


627-627: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


663-663: Abstract raise to an inner function

(TRY301)


667-667: Do not catch blind exception: Exception

(BLE001)


668-668: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


669-669: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


705-705: Abstract raise to an inner function

(TRY301)


709-709: Do not catch blind exception: Exception

(BLE001)


710-710: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


711-711: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


722-722: Abstract raise to an inner function

(TRY301)


729-729: Abstract raise to an inner function

(TRY301)


733-733: Do not catch blind exception: Exception

(BLE001)


734-734: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


735-735: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


746-746: Abstract raise to an inner function

(TRY301)


748-748: Local variable response is assigned to but never used

Remove assignment to unused variable response

(F841)


750-750: Consider moving this statement to an else block

(TRY300)


754-754: Do not catch blind exception: Exception

(BLE001)


755-755: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


756-756: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


785-785: Consider moving this statement to an else block

(TRY300)


787-787: Do not catch blind exception: Exception

(BLE001)


788-788: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


789-789: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


793-793: Unused function argument: brand_id

(ARG001)


794-794: Unused function argument: industry

(ARG001)


796-796: Unused function argument: location

(ARG001)


830-830: Consider moving this statement to an else block

(TRY300)


832-832: Do not catch blind exception: Exception

(BLE001)


833-833: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


834-834: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


837-837: Unused function argument: brand_id

(ARG001)


845-845: Abstract raise to an inner function

(TRY301)


860-865: Consider moving this statement to an else block

(TRY300)


869-869: Do not catch blind exception: Exception

(BLE001)


870-870: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


871-871: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


916-916: Consider moving this statement to an else block

(TRY300)


918-918: Do not catch blind exception: Exception

(BLE001)


919-919: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


920-920: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


949-949: Do not catch blind exception: Exception

(BLE001)


950-950: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


951-951: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


964-964: Consider moving this statement to an else block

(TRY300)


966-966: Do not catch blind exception: Exception

(BLE001)


967-967: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


968-968: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


992-992: Abstract raise to an inner function

(TRY301)


994-994: Do not catch blind exception: Exception

(BLE001)


995-995: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


996-996: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1011-1011: Abstract raise to an inner function

(TRY301)


1018-1018: Abstract raise to an inner function

(TRY301)


1022-1022: Do not catch blind exception: Exception

(BLE001)


1023-1023: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1024-1024: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1074-1074: Consider moving this statement to an else block

(TRY300)


1078-1078: Do not catch blind exception: Exception

(BLE001)


1079-1079: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1080-1080: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1095-1095: Abstract raise to an inner function

(TRY301)


1102-1102: Abstract raise to an inner function

(TRY301)


1117-1117: Consider moving this statement to an else block

(TRY300)


1121-1121: Do not catch blind exception: Exception

(BLE001)


1122-1122: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1123-1123: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1142-1142: Abstract raise to an inner function

(TRY301)


1147-1147: Abstract raise to an inner function

(TRY301)


1159-1159: Abstract raise to an inner function

(TRY301)


1163-1163: Do not catch blind exception: Exception

(BLE001)


1164-1164: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1165-1165: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1205-1205: Do not catch blind exception: Exception

(BLE001)


1206-1206: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1207-1207: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1246-1246: Consider moving this statement to an else block

(TRY300)


1250-1250: Do not catch blind exception: Exception

(BLE001)


1251-1251: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1252-1252: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1266-1266: Abstract raise to an inner function

(TRY301)


1284-1284: Consider moving this statement to an else block

(TRY300)


1288-1288: Do not catch blind exception: Exception

(BLE001)


1289-1289: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1290-1290: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1309-1309: Abstract raise to an inner function

(TRY301)


1317-1317: Abstract raise to an inner function

(TRY301)


1321-1321: Do not catch blind exception: Exception

(BLE001)


1322-1322: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1323-1323: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1361-1361: Do not catch blind exception: Exception

(BLE001)


1362-1362: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1363-1363: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1387-1387: Abstract raise to an inner function

(TRY301)


1409-1409: Abstract raise to an inner function

(TRY301)


1413-1413: Do not catch blind exception: Exception

(BLE001)


1414-1414: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1415-1415: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1430-1430: Abstract raise to an inner function

(TRY301)


1438-1438: Consider moving this statement to an else block

(TRY300)


1442-1442: Do not catch blind exception: Exception

(BLE001)


1443-1443: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1444-1444: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1465-1465: Abstract raise to an inner function

(TRY301)


1474-1474: Abstract raise to an inner function

(TRY301)


1478-1478: Do not catch blind exception: Exception

(BLE001)


1479-1479: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1480-1480: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1523-1523: Do not catch blind exception: Exception

(BLE001)


1524-1524: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1555-1555: Do not catch blind exception: Exception

(BLE001)


1556-1556: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1579-1579: Do not catch blind exception: Exception

(BLE001)


1580-1580: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1610-1610: Do not catch blind exception: Exception

(BLE001)


1611-1611: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1625-1625: Do not catch blind exception: Exception

(BLE001)


1626-1626: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1627-1627: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

Backend/app/routes/ai_query.py

40-40: Abstract raise to an inner function

(TRY301)


138-138: Do not catch blind exception: Exception

(BLE001)


139-139: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


147-147: Parenthesize a and b expressions when chaining and and or together, to make the precedence clear

Parenthesize the and subexpression

(RUF021)


182-182: Consider moving this statement to an else block

(TRY300)


185-185: Do not catch blind exception: Exception

(BLE001)


186-186: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


187-187: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


200-200: Do not catch blind exception: Exception

(BLE001)


201-201: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


202-202: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


212-212: Abstract raise to an inner function

(TRY301)


214-217: Consider moving this statement to an else block

(TRY300)


220-220: Do not catch blind exception: Exception

(BLE001)


221-221: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


222-222: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


233-237: Consider moving this statement to an else block

(TRY300)


238-238: Do not catch blind exception: Exception

(BLE001)


239-239: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

Backend/app/routes/contracts.py

233-233: Abstract raise to an inner function

(TRY301)


235-235: Do not catch blind exception: Exception

(BLE001)


236-236: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


236-236: Use explicit conversion flag

Replace with conversion flag

(RUF010)


262-262: Do not catch blind exception: Exception

(BLE001)


263-263: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


263-263: Use explicit conversion flag

Replace with conversion flag

(RUF010)


272-272: Abstract raise to an inner function

(TRY301)


278-278: Do not catch blind exception: Exception

(BLE001)


279-279: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


279-279: Use explicit conversion flag

Replace with conversion flag

(RUF010)


292-292: Abstract raise to an inner function

(TRY301)


298-298: Do not catch blind exception: Exception

(BLE001)


299-299: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


299-299: Use explicit conversion flag

Replace with conversion flag

(RUF010)


308-308: Abstract raise to an inner function

(TRY301)


310-310: Consider moving this statement to an else block

(TRY300)


314-314: Do not catch blind exception: Exception

(BLE001)


315-315: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


315-315: Use explicit conversion flag

Replace with conversion flag

(RUF010)


333-333: Abstract raise to an inner function

(TRY301)


335-335: Do not catch blind exception: Exception

(BLE001)


336-336: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


336-336: Use explicit conversion flag

Replace with conversion flag

(RUF010)


362-362: Do not catch blind exception: Exception

(BLE001)


363-363: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


363-363: Use explicit conversion flag

Replace with conversion flag

(RUF010)


372-372: Abstract raise to an inner function

(TRY301)


378-378: Do not catch blind exception: Exception

(BLE001)


379-379: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


379-379: Use explicit conversion flag

Replace with conversion flag

(RUF010)


397-397: Abstract raise to an inner function

(TRY301)


399-399: Do not catch blind exception: Exception

(BLE001)


400-400: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


400-400: Use explicit conversion flag

Replace with conversion flag

(RUF010)


410-410: Do not catch blind exception: Exception

(BLE001)


411-411: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


411-411: Use explicit conversion flag

Replace with conversion flag

(RUF010)


424-424: Abstract raise to an inner function

(TRY301)


430-430: Do not catch blind exception: Exception

(BLE001)


431-431: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


431-431: Use explicit conversion flag

Replace with conversion flag

(RUF010)


440-440: Abstract raise to an inner function

(TRY301)


442-442: Consider moving this statement to an else block

(TRY300)


446-446: Do not catch blind exception: Exception

(BLE001)


447-447: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


447-447: Use explicit conversion flag

Replace with conversion flag

(RUF010)


465-465: Abstract raise to an inner function

(TRY301)


467-467: Do not catch blind exception: Exception

(BLE001)


468-468: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


468-468: Use explicit conversion flag

Replace with conversion flag

(RUF010)


478-478: Do not catch blind exception: Exception

(BLE001)


479-479: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


479-479: Use explicit conversion flag

Replace with conversion flag

(RUF010)


492-492: Abstract raise to an inner function

(TRY301)


498-498: Do not catch blind exception: Exception

(BLE001)


499-499: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


499-499: Use explicit conversion flag

Replace with conversion flag

(RUF010)


508-508: Abstract raise to an inner function

(TRY301)


510-510: Consider moving this statement to an else block

(TRY300)


514-514: Do not catch blind exception: Exception

(BLE001)


515-515: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


515-515: Use explicit conversion flag

Replace with conversion flag

(RUF010)


534-534: Abstract raise to an inner function

(TRY301)


536-536: Do not catch blind exception: Exception

(BLE001)


537-537: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


537-537: Use explicit conversion flag

Replace with conversion flag

(RUF010)


547-547: Do not catch blind exception: Exception

(BLE001)


548-548: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


548-548: Use explicit conversion flag

Replace with conversion flag

(RUF010)


561-561: Abstract raise to an inner function

(TRY301)


567-567: Do not catch blind exception: Exception

(BLE001)


568-568: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


568-568: Use explicit conversion flag

Replace with conversion flag

(RUF010)


577-577: Abstract raise to an inner function

(TRY301)


579-579: Consider moving this statement to an else block

(TRY300)


583-583: Do not catch blind exception: Exception

(BLE001)


584-584: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


584-584: Use explicit conversion flag

Replace with conversion flag

(RUF010)


603-603: Abstract raise to an inner function

(TRY301)


605-605: Do not catch blind exception: Exception

(BLE001)


606-606: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


606-606: Use explicit conversion flag

Replace with conversion flag

(RUF010)


616-616: Do not catch blind exception: Exception

(BLE001)


617-617: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


617-617: Use explicit conversion flag

Replace with conversion flag

(RUF010)


626-626: Abstract raise to an inner function

(TRY301)


628-628: Consider moving this statement to an else block

(TRY300)


632-632: Do not catch blind exception: Exception

(BLE001)


633-633: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


633-633: Use explicit conversion flag

Replace with conversion flag

(RUF010)


647-647: Do not catch blind exception: Exception

(BLE001)


648-648: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


648-648: Use explicit conversion flag

Replace with conversion flag

(RUF010)


662-662: Abstract raise to an inner function

(TRY301)


664-664: Do not catch blind exception: Exception

(BLE001)


665-665: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


665-665: Use explicit conversion flag

Replace with conversion flag

(RUF010)


684-684: Do not catch blind exception: Exception

(BLE001)


685-685: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


685-685: Use explicit conversion flag

Replace with conversion flag

(RUF010)


694-694: Abstract raise to an inner function

(TRY301)


696-696: Consider moving this statement to an else block

(TRY300)


700-700: Do not catch blind exception: Exception

(BLE001)


701-701: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


701-701: Use explicit conversion flag

Replace with conversion flag

(RUF010)


739-739: Do not catch blind exception: Exception

(BLE001)


740-740: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


740-740: Use explicit conversion flag

Replace with conversion flag

(RUF010)


773-773: Do not catch blind exception: Exception

(BLE001)


774-774: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


774-774: Use explicit conversion flag

Replace with conversion flag

(RUF010)

port=5432
dbname=postgres
GROQ_API_KEY=
GROQ_API_KEY=your_groq_api_key_here
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Avoid real secrets; align variable names with backend; document Redis SSL.

  • Don’t commit real keys; keep placeholders. Add a header comment reminding contributors to copy this file to .env and never commit real values.
  • Verify the backend actually reads GROQ_API_KEY, YOUTUBE_API_KEY, and Redis vars as named here.
  • Redis Cloud typically requires TLS. The example suggests non‑SSL; either mark REDIS_SSL=true in examples or annotate when to disable.

Run to confirm env var usage:

Also applies to: 10-10, 12-15


🏁 Script executed:

#!/bin/bash
rg -nP --type=py -C2 '\bos\.getenv\(\s*"(GROQ_API_KEY|YOUTUBE_API_KEY|REDIS_HOST|REDIS_PORT|REDIS_PASSWORD)"'

Length of output: 1746


🏁 Script executed:

#!/bin/bash
# Get full redis_client.py to see complete redis.Redis() call
cat Backend/app/services/redis_client.py

Length of output: 834


Add REDIS_SSL to .env-example and make SSL configurable in redis_client.py.

Env var names are correctly aligned with backend usage (verified: GROQ_API_KEY, YOUTUBE_API_KEY, REDIS_HOST, REDIS_PORT, REDIS_PASSWORD all read correctly).

However, the .env-example is missing REDIS_SSL configuration. Currently, redis_client.py hardcodes ssl=False (line 14), which:

  • Cannot be overridden per deployment
  • Contradicts typical Redis Cloud requirements (which mandate TLS)
  • Makes the comment "works without SSL" misleading

Add REDIS_SSL=true to .env-example and update redis_client.py to read it: ssl=os.getenv("REDIS_SSL", "false").lower() == "true" (or similar, depending on your parsing preference).

🤖 Prompt for AI Agents
In Backend/.env-example around line 6 add a REDIS_SSL entry (e.g.,
REDIS_SSL=true) so deployments can opt into TLS; then update
Backend/redis_client.py (around the current ssl=False on line 14) to read the
env var and set ssl accordingly using something like ssl =
os.getenv("REDIS_SSL", "false").lower() == "true", and update the inline comment
to reflect that SSL is now configurable and defaults to false.

Comment on lines +22 to +31
class AIQueryResponse(BaseModel):
intent: str
route: Optional[str] = None
parameters: Dict[str, Any] = {}
follow_up_needed: bool = False
follow_up_question: Optional[str] = None
explanation: str
original_query: str
timestamp: str

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Response model drops session_id/result; include them and fix mutable defaults.

FastAPI strips fields not in the response_model. Frontend expects session_id and result, but they’re not defined, so they won’t be returned. Also avoid {} as a default.

-from pydantic import BaseModel
+from pydantic import BaseModel, Field
@@
 class AIQueryResponse(BaseModel):
     intent: str
     route: Optional[str] = None
-    parameters: Dict[str, Any] = {}
+    parameters: Dict[str, Any] = Field(default_factory=dict)
     follow_up_needed: bool = False
     follow_up_question: Optional[str] = None
     explanation: str
     original_query: str
     timestamp: str
+    session_id: Optional[str] = None
+    result: Optional[Any] = None
+    error: Optional[str] = None
@@
-        response = AIQueryResponse(
+        response = AIQueryResponse(
             intent=result.get("intent", "unknown"),
             route=result.get("route"),
             parameters=params,
-            follow_up_needed=not all_params_present and not only_optional_params or api_error is not None,
+            follow_up_needed=((not all_params_present and not only_optional_params) or (api_error is not None)),
             follow_up_question=(result.get("follow_up_question") if not all_params_present and not only_optional_params else None),
             explanation=(result.get("explanation", "") if not api_error else f"An error occurred while processing your request: {api_error}"),
             original_query=result.get("original_query", request.query),
             timestamp=result.get("timestamp", ""),
         )
@@
-        response_dict = response.dict()
+        response_dict = response.dict()
@@
-        if api_result is not None:
-            response_dict["result"] = api_result
-        if api_error is not None:
-            response_dict["error"] = api_error
-        return response_dict
+        if api_result is not None:
+            response_dict["result"] = api_result
+        if api_error is not None:
+            response_dict["error"] = api_error
+        return response_dict

Also applies to: 143-152, 154-182

🤖 Prompt for AI Agents
In Backend/app/routes/ai_query.py around lines 22-31 (and also apply same fixes
at 143-152 and 154-182), the AIQueryResponse Pydantic model is missing the
session_id and result fields which causes FastAPI to strip them from responses,
and it uses a mutable default (parameters: Dict = {}), which is unsafe; add
session_id: Optional[str] (or str if required) and result: Optional[Dict[str,
Any]] (or the correct type) to the model, and replace mutable defaults with None
and proper default_factory where needed (e.g., parameters: Dict[str, Any] =
Field(default_factory=dict) or parameters: Optional[Dict[str, Any]] = None) so
fields are returned to the frontend and defaults are immutable/initialized
per-instance.

Comment on lines +48 to +60
# Security Helper Functions
def validate_brand_access(brand_id: str, current_user_id: str):
"""Validate that the current user can access the brand data"""
if brand_id != current_user_id:
raise HTTPException(status_code=403, detail="Access denied: You can only access your own data")
return True

def require_brand_role(user_role: str):
"""Ensure user has brand role"""
if user_role != "brand":
raise HTTPException(status_code=403, detail="Access denied: Brand role required")
return True

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Authz missing across router; helpers unused. Enforce user–brand binding.

All routes trust a client‑supplied brand_id. Use Depends(get_current_user), require brand role, and enforce validate_brand_access(...) consistently. Example patch for one route (apply same pattern elsewhere):

@@
-from fastapi import APIRouter, HTTPException, Depends, Query
+from fastapi import APIRouter, HTTPException, Depends, Query
@@
 router = APIRouter(prefix="/api/brand", tags=["Brand Dashboard"])
@@
-@router.get("/campaigns")
-async def get_brand_campaigns(brand_id: str = Query(..., description="Brand user ID")):
+# TODO: import your actual auth dependency
+from ..services.auth import get_current_user  # adjust path
+
+@router.get("/campaigns")
+async def get_brand_campaigns(
+    brand_id: str = Query(..., description="Brand user ID"),
+    current_user=Depends(get_current_user),
+):
+    require_brand_role(current_user["role"])
+    validate_brand_access(brand_id, current_user["id"])
@@
-    campaigns = safe_supabase_query(
+    campaigns = safe_supabase_query(
         lambda: supabase.table("sponsorships").select("*").eq("brand_id", brand_id).execute(),
         "Failed to fetch brand campaigns"
     )

Run to find all endpoints still trusting brand_id without Depends(get_current_user):


🏁 Script executed:

#!/bin/bash
rg -nP 'def\s+\w+\(.*brand_id:\s*str\s*=\s*Query' -C1 Backend/app/routes | sed -n '1,200p'

Length of output: 6297


Now I need to search the codebase directly to verify the actual state of auth and helper usage:

Now let me search the codebase directly to verify the helpers' usage and auth status:


#!/bin/bash
# Check if helpers are called anywhere in the codebase
echo "=== Searching for calls to validate_brand_access ==="
rg -n 'validate_brand_access' Backend/app/routes/

echo ""
echo "=== Searching for calls to require_brand_role ==="
rg -n 'require_brand_role' Backend/app/routes/

echo ""
echo "=== Searching for get_current_user or auth dependency ==="
rg -n 'get_current_user|from.*auth import|from.*security import' Backend/app/routes/ -C2

🏁 Script executed:

#!/bin/bash
# Check if helpers are called anywhere in the codebase
echo "=== Searching for calls to validate_brand_access ==="
rg -n 'validate_brand_access' Backend/app/routes/

echo ""
echo "=== Searching for calls to require_brand_role ==="
rg -n 'require_brand_role' Backend/app/routes/

echo ""
echo "=== Searching for get_current_user or auth dependency ==="
rg -n 'get_current_user|from.*auth import|from.*security import' Backend/app/routes/ -C2

echo ""
echo "=== Sample endpoint implementation (lines 99-115) ==="
sed -n '99,115p' Backend/app/routes/brand_dashboard.py

echo ""
echo "=== Another sample endpoint (lines 162-178) ==="
sed -n '162,178p' Backend/app/routes/brand_dashboard.py

Length of output: 1785


Authorization bypass: 21 endpoints trust client-supplied brand_id without current user validation.

All endpoints accept brand_id via Query parameter and query Supabase directly without verifying the requesting user owns that brand. The helper functions validate_brand_access() and require_brand_role() are defined but never called. No auth dependency (get_current_user) exists in the router.

Critical fix: Inject current user via Depends(get_current_user) in all 21 endpoints and enforce validate_brand_access(brand_id, current_user["id"]) before any Supabase query. Remove dead helpers or use them consistently.

Example pattern to apply to all endpoints:

-@router.get("/dashboard/overview", response_model=DashboardOverviewResponse)
-async def get_dashboard_overview(brand_id: str = Query(..., description="Brand user ID")):
+@router.get("/dashboard/overview", response_model=DashboardOverviewResponse)
+async def get_dashboard_overview(
+    brand_id: str = Query(..., description="Brand user ID"),
+    current_user=Depends(get_current_user),
+):
     """Get dashboard overview with key metrics for a brand"""
     validate_uuid_format(brand_id, "brand_id")
+    require_brand_role(current_user["role"])
+    validate_brand_access(brand_id, current_user["id"])

Affected endpoints: lines 99, 162, 285, 356, 463, 634, 649, 714, 738, 763, 837, 878, 923, 958, 1032, 1083, 1168, 1215, 1255, 1326, 1418, 1483.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Backend/app/routes/brand_dashboard.py around lines 48 to 60 and for the
listed affected endpoints (lines 99, 162, 285, 356, 463, 634, 649, 714, 738,
763, 837, 878, 923, 958, 1032, 1083, 1168, 1215, 1255, 1326, 1418, 1483): the
router currently trusts a client-supplied brand_id and never injects or
validates the current user; update each endpoint signature to accept
current_user via Depends(get_current_user) and immediately call
validate_brand_access(brand_id, current_user["id"]) (and
require_brand_role(current_user["role"]) where appropriate) before any Supabase
queries, remove or keep the helper functions consistently (delete if unused),
and ensure no Supabase queries execute until access checks pass.

Comment on lines +61 to +66
def validate_uuid_format(id_value: str, field_name: str = "ID"):
"""Validate UUID format"""
if not id_value or len(id_value) != 36:
raise HTTPException(status_code=400, detail=f"Invalid {field_name} format")
return True

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Robust UUID validation.

Length check is weak. Parse using uuid.UUID to reject malformed inputs.

 def validate_uuid_format(id_value: str, field_name: str = "ID"):
-    """Validate UUID format"""
-    if not id_value or len(id_value) != 36:
-        raise HTTPException(status_code=400, detail=f"Invalid {field_name} format")
-    return True
+    """Validate UUID format"""
+    try:
+        uuid.UUID(str(id_value))
+    except (ValueError, TypeError):
+        raise HTTPException(status_code=400, detail=f"Invalid {field_name} format")
+    return True
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def validate_uuid_format(id_value: str, field_name: str = "ID"):
"""Validate UUID format"""
if not id_value or len(id_value) != 36:
raise HTTPException(status_code=400, detail=f"Invalid {field_name} format")
return True
def validate_uuid_format(id_value: str, field_name: str = "ID"):
"""Validate UUID format"""
try:
uuid.UUID(str(id_value))
except (ValueError, TypeError):
raise HTTPException(status_code=400, detail=f"Invalid {field_name} format")
return True
🤖 Prompt for AI Agents
In Backend/app/routes/brand_dashboard.py around lines 61 to 66, the UUID
validation currently only checks length which is insufficient; replace the
length check with actual parsing using Python's uuid.UUID to validate format:
attempt uuid.UUID(id_value, version=None) inside a try/except, and on ValueError
raise HTTPException(status_code=400, detail=f"Invalid {field_name} format");
keep the function signature and return True on success so callers are unchanged.

Comment on lines +302 to +310
for campaign in campaigns:
campaign_metrics = safe_supabase_query(
lambda: supabase.table("campaign_metrics").select("*").eq("campaign_id", campaign["id"]).execute(),
f"Failed to fetch metrics for campaign {campaign['id']}"
)

# Get latest metrics for this campaign
latest_metrics = campaign_metrics[-1] if campaign_metrics else {}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Latest metrics selection is unreliable. Order by timestamp and limit 1.

Relying on [-1] without ordering can pick arbitrary rows.

-            campaign_metrics = safe_supabase_query(
-                lambda: supabase.table("campaign_metrics").select("*").eq("campaign_id", campaign["id"]).execute(),
-                f"Failed to fetch metrics for campaign {campaign['id']}"
-            )
-            
-            # Get latest metrics for this campaign
-            latest_metrics = campaign_metrics[-1] if campaign_metrics else {}
+            campaign_metrics = safe_supabase_query(
+                lambda: supabase.table("campaign_metrics")
+                    .select("*")
+                    .eq("campaign_id", campaign["id"])
+                    .order("recorded_at", desc=True)
+                    .limit(1)
+                    .execute(),
+                f"Failed to fetch metrics for campaign {campaign['id']}"
+            )
+            latest_metrics = campaign_metrics[0] if campaign_metrics else {}
🧰 Tools
🪛 Ruff (0.14.1)

304-304: Function definition does not bind loop variable campaign

(B023)

🤖 Prompt for AI Agents
In Backend/app/routes/brand_dashboard.py around lines 302 to 310, selecting the
"latest" metrics by indexing campaign_metrics[-1] is unreliable because the
query doesn't guarantee order; change the supabase query to order by the
timestamp (or created_at) field descending and limit to 1 so you fetch only the
newest row, then use the first element (if present) as latest_metrics; keep
using safe_supabase_query for the call and update the subsequent logic to handle
a single-row response (check for empty result before accessing index 0).

Comment on lines +162 to +185
<button
onClick={() => setShowCreateModal(true)}
style={{
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
color: '#fff',
border: 'none',
borderRadius: '12px',
padding: '12px 24px',
fontSize: '16px',
fontWeight: '600',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '8px',
transition: 'all 0.2s ease',
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.3)'
}}
onMouseEnter={(e) => e.currentTarget.style.transform = 'translateY(-2px)'}
onMouseLeave={(e) => e.currentTarget.style.transform = 'translateY(0)'}
>
<Plus size={20} />
Create Contract
</button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Create button toggles state but no modal is rendered.

showCreateModal is never used in JSX, so “Create Contract” does nothing.

-  const [showCreateModal, setShowCreateModal] = useState(false);
+  const [showCreateModal, setShowCreateModal] = useState(false);
...
-      {/* Contract Details Modal */}
+      {/* Contract Details Modal */}
       <ContractDetailsModal 
         contract={selectedContract}
         onClose={() => setSelectedContract(null)}
       />
+      {/* Create Contract Modal (placeholder until backend wiring) */}
+      {showCreateModal && (
+        <div role="dialog" aria-modal="true" style={{ background: 'rgba(0,0,0,0.6)', position:'fixed', inset:0, display:'grid', placeItems:'center' }}>
+          <div style={{ background:'#1e1e1e', padding:24, borderRadius:12 }}>
+            <h3 style={{ marginBottom:12 }}>Create Contract</h3>
+            <p style={{ color:'#a0a0a0' }}>Form coming soon…</p>
+            <button onClick={() => setShowCreateModal(false)} style={{ marginTop:16 }}>Close</button>
+          </div>
+        </div>
+      )}

Also applies to: 517-521

🤖 Prompt for AI Agents
In Frontend/src/pages/Contracts.tsx around lines 162-185 (also apply same fix at
517-521), the onClick sets showCreateModal but the JSX never reads it so no
modal appears; update the component to render the create-contract modal
conditionally when showCreateModal === true (e.g., {showCreateModal &&
<CreateContractModal .../>}), wire a close handler that calls
setShowCreateModal(false), pass any required props (callbacks to refresh list or
initial data), and ensure the CreateContractModal import is present at top of
the file; repeat the same conditional render/close wiring for the other
occurrence at lines 517-521.

Comment on lines +29 to +43
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${AI_API_BASE_URL}${endpoint}`;

try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Header merge bug can drop Content-Type.

...options after headers can overwrite the composed headers. Merge safely.

-      const response = await fetch(url, {
-        headers: {
-          'Content-Type': 'application/json',
-          ...options.headers,
-        },
-        ...options,
-      });
+      const { headers: optHeaders, ...rest } = options;
+      const mergedHeaders = {
+        'Content-Type': 'application/json',
+        ...(optHeaders as Record<string, string> | undefined),
+      };
+      const response = await fetch(url, {
+        ...rest,
+        headers: mergedHeaders,
+      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${AI_API_BASE_URL}${endpoint}`;
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${AI_API_BASE_URL}${endpoint}`;
try {
const { headers: optHeaders, ...rest } = options;
const mergedHeaders = {
'Content-Type': 'application/json',
...(optHeaders as Record<string, string> | undefined),
};
const response = await fetch(url, {
...rest,
headers: mergedHeaders,
});
🤖 Prompt for AI Agents
In Frontend/src/services/aiApi.ts around lines 29 to 43, the current request
build spreads ...options after setting headers which can overwrite the composed
headers and drop Content-Type; fix by extracting headers from options first
(e.g., const { headers, ...rest } = options) and then pass fetch an options
object that sets headers: { 'Content-Type': 'application/json', ...(headers ||
{}) } and spreads rest (not the original options) so custom headers can override
the default safely without being overwritten.

Comment on lines +72 to +97
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${API_BASE_URL}${endpoint}`;

try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
}

return await response.json();
} catch (error) {
console.error(`API Error (${endpoint}):`, error);
throw error;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle 204/empty responses to avoid JSON parse errors.

DELETE endpoints and some PUTs may return no content.

-      return await response.json();
+      const text = await response.text();
+      return (text ? JSON.parse(text) : undefined) as T;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${API_BASE_URL}${endpoint}`;
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`API Error (${endpoint}):`, error);
throw error;
}
}
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${API_BASE_URL}${endpoint}`;
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
}
const text = await response.text();
return (text ? JSON.parse(text) : undefined) as T;
} catch (error) {
console.error(`API Error (${endpoint}):`, error);
throw error;
}
}
🤖 Prompt for AI Agents
In Frontend/src/services/brandApi.ts around lines 72 to 97 the makeRequest
method assumes every successful response has JSON and will throw when a 204 or
an empty-body response is returned; update the success path to detect no-content
responses (e.g., response.status === 204 or empty body/Content-Length === '0')
and return a sensible empty value (null or undefined cast to T) instead of
calling response.json(), otherwise read response.text() and if the text is empty
return null/undefined, else parse JSON and return it; keep existing error
handling for non-ok responses.

Comment on lines +153 to +157
async deleteCampaign(campaignId: string, brandId: string): Promise<void> {
return this.makeRequest<void>(`/campaigns/${campaignId}?brand_id=${brandId}`, {
method: 'DELETE',
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

DELETE/PUT callers rely on server body.

After 204 handling in makeRequest, ensure callers don’t assume JSON. Consider returning { ok: true } or void consistently.

Also applies to: 219-236


🏁 Script executed:

#!/bin/bash
# Find methods calling DELETE/PUT that return void across services
rg -nP 'makeRequest<\s*void\s*>\s*\(' -C2

Length of output: 383


I need to investigate further to verify the review comment. Let me search for the additional methods mentioned and examine the makeRequest implementation.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the code at lines 219-236 in brandApi.ts to see what the review also applies to
cat Frontend/src/services/brandApi.ts | sed -n '219,236p'

Length of output: 593


🏁 Script executed:

#!/bin/bash
# Search for makeRequest implementation to understand 204 handling
rg -nP 'makeRequest\s*<' Frontend/src/services/brandApi.ts -A10 | head -50

Length of output: 1740


🏁 Script executed:

#!/bin/bash
# Find all DELETE/PUT methods in brandApi.ts (not just void returns)
rg -nP "method:\s*['\"]?(DELETE|PUT)" Frontend/src/services/brandApi.ts -B5 -A3

Length of output: 1426


I need to see the complete makeRequest implementation to understand how it handles 204 responses.


---

🏁 Script executed:

```shell
#!/bin/bash
# Get the complete makeRequest implementation
sed -n '72,100p' Frontend/src/services/brandApi.ts

Length of output: 775


🏁 Script executed:

#!/bin/bash
# Search for response parsing and 204 handling in makeRequest
rg -nP '(status|204|No Content|response\.)' Frontend/src/services/brandApi.ts -A1 -B1 | head -80

Length of output: 957


🏁 Script executed:

#!/bin/bash
# Find callers of deleteCampaign to see how they handle the response
rg -nP 'deleteCampaign\s*\(' -B5 -A5 --type=ts --type=tsx

Length of output: 27


Let me search more carefully for void-returning methods and their callers.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find all methods returning Promise<void> in brandApi.ts
rg -nP 'Promise<\s*void\s*>' Frontend/src/services/brandApi.ts -B3

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Find callers of deleteCampaign (corrected search without tsx flag)
rg -nP 'deleteCampaign\s*\(' --type=ts -B3 -A3

Length of output: 1098


🏁 Script executed:

#!/bin/bash
# Check if there are other DELETE methods
rg -nP "method:\s*['\"]DELETE" Frontend/src/services/brandApi.ts -B5 -A2

Length of output: 241


Handle 204 No Content responses in deleteCampaign.

The makeRequest method unconditionally calls response.json() for all successful responses (line 92), but a 204 No Content response (standard for DELETE operations) has no body. This will cause a JSON parsing error. The current code at lines 153-157 will fail if the server returns 204. Modify makeRequest to detect 204 status and return undefined instead of attempting to parse the response body.

🤖 Prompt for AI Agents
In Frontend/src/services/brandApi.ts around lines 153-157 (deleteCampaign) and
the makeRequest implementation around line ~92, makeRequest currently calls
response.json() for all successful responses which breaks on 204 No Content;
update makeRequest to check if response.status === 204 (or response.status >=
200 && response.status < 300 && response.status !== 204) and return undefined
immediately for 204 responses instead of calling response.json(), otherwise
continue to parse and return the JSON as before; ensure the return type remains
compatible with callers like deleteCampaign which expect Promise<void>.

Comment on lines +202 to +212
async updateApplicationStatus(
applicationId: string,
status: string,
notes?: string,
brandId?: string
): Promise<Application> {
return this.makeRequest<Application>(`/applications/${applicationId}?brand_id=${brandId}`, {
method: 'PUT',
body: JSON.stringify({ status, notes }),
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

brandId is optional in signature but required in the URL.

Avoid brand_id=undefined. Make it required.

-  async updateApplicationStatus(
-    applicationId: string,
-    status: string,
-    notes?: string,
-    brandId?: string
-  ): Promise<Application> {
+  async updateApplicationStatus(
+    applicationId: string,
+    status: string,
+    notes: string | undefined,
+    brandId: string
+  ): Promise<Application> {
🤖 Prompt for AI Agents
In Frontend/src/services/brandApi.ts around lines 202-212, the function accepts
an optional brandId but interpolates it into the URL causing
`brand_id=undefined`; make brandId required (remove the `?` from the parameter)
and update the function signature to require a string brandId, then ensure the
URL always includes the provided brandId; if you cannot change all call sites
immediately, add a defensive runtime check at the start of the function that
throws a clear error when brandId is missing so the request never includes
`undefined`.

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