Skip to content

Conversation

@Saahi30
Copy link
Collaborator

@Saahi30 Saahi30 commented Oct 7, 2025

📝 Description

This pull request introduces significant updates to the brand-dashboard-api-part-1 branch, focusing on enhancing the user experience, backend functionality, and frontend-backend integration. The changes include a revamped UI, new database tables, FastAPI models, Pydantic schemas, API routes, and AI-powered search features. Additionally, several fixes and improvements have been made to ensure a seamless and secure user experience.

🔧 Changes Made

Frontend:
Revamped the user interface for a modern and intuitive design.
Added smart features to the search bar for improved usability.
Resolved CSS import order issues and TypeScript errors.
Improved AI response display with better formatting.

Backend:
Added new database tables to support additional features.
Implemented FastAPI models and Pydantic schemas for data validation.
Developed new API routes and endpoints for enhanced functionality.
Made minor security improvements to the API.
Separated the AI API service to address 404 errors for AI queries.

Integration:
Connected the frontend to the backend with API integration.
Integrated AI-powered search functionality.
Added documentation for frontend-backend integration.

Fixes:
Fixed missing SponsorshipCreate import.
Removed metric cards from the brand dashboard.

📷 Screenshots or Visual Changes (if applicable)

✅ Checklist

  • [ ✅ ] I have read the contributing guidelines.

Summary by CodeRabbit

  • New Features

    • Introduced a Brand Dashboard with a new sidebar UI, campaign management, creator discovery/matching, contracts, applications, payments, and performance/revenue analytics.
    • Added AI-powered query/search across brand operations with routable results and test endpoint access.
    • Optional Dashboard item in user menu (can be hidden via prop).
    • Added a new display font for enhanced styling.
  • Documentation

    • Added a Frontend–Backend Integration guide with setup and testing steps.
  • Chores

    • Updated environment example with AI key placeholder.
    • Added AI-related backend dependencies.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Adds brand dashboard and AI query features end-to-end: new FastAPI routers (brand_dashboard, ai_query), models, schemas, SQL tables/seeds, and services (Groq-backed AI router). Frontend adds API clients, a dashboard hook, a redesigned Brand Dashboard page with AI search, minor component tweaks, a new font import, and integration README.

Changes

Cohort / File(s) Summary of changes
Env & Dependencies
Backend/.env-example, Backend/requirements.txt
Adds GROQ_API_KEY placeholder; adds groq and openai dependencies.
Backend App Routing
Backend/app/main.py
Includes new routers: brand_dashboard and ai_query.
AI Query Backend
Backend/app/routes/ai_query.py, Backend/app/services/ai_router.py
New AI API endpoints (/api/ai: query, routes, route info, test); Groq-powered AIRouter with route catalog, prompt, result parsing, and helpers.
Brand Dashboard Backend
Backend/app/routes/brand_dashboard.py
New brand dashboard API: overview, profile CRUD, campaigns CRUD, creator matching/search, applications, payments, contracts, analytics, campaign metrics; Supabase interactions and validations.
Data Models & Schemas
Backend/app/models/models.py, Backend/app/schemas/schema.py, Backend/sql.txt
Adds ORM models (BrandProfile, CampaignMetrics, Contract, CreatorMatch); extensive Pydantic schemas for dashboard/analytics/payment/contracts; SQL tables and sample data for new entities.
Frontend Services
Frontend/src/services/brandApi.ts, Frontend/src/services/aiApi.ts
New API client services for brand dashboard and AI endpoints with typed interfaces and request helpers.
Frontend State Hook
Frontend/src/hooks/useBrandDashboard.ts
New hook centralizing dashboard state, loaders, CRUD, analytics, creator search, and AI query integration.
Dashboard UI Overhaul
Frontend/src/pages/Brand/Dashboard.tsx
Replaces page with new BrandDashboard: collapsible sidebar, AI search UI, quick actions, loading/error handling, and useBrandDashboard integration.
UI Components Tweaks
Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx, Frontend/src/components/user-nav.tsx
Adjusts list key to name-index; adds optional prop showDashboard to UserNav with conditional rendering.
Auth Context Minor
Frontend/src/context/AuthContext.tsx
Boolean coercion for onboarding flag.
Styling
Frontend/src/index.css
Adds Orbitron font import.
Docs
Frontend/README-INTEGRATION.md
Adds frontend-backend integration guide.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Brand Dashboard UI
  participant Hook as useBrandDashboard
  participant BAPI as brandApi (FE)
  participant API as FastAPI /api/brand
  participant DB as Supabase

  UI->>Hook: mount / refreshData()
  Hook->>BAPI: getDashboardOverview(brandId)
  BAPI->>API: GET /api/brand/overview?brand_id=...
  API->>DB: Fetch campaigns/profile/apps/payments/matches
  DB-->>API: Aggregated records
  API-->>BAPI: DashboardOverviewResponse
  BAPI-->>Hook: data
  Hook-->>UI: state updated (loading -> idle)
Loading
sequenceDiagram
  autonumber
  participant UI as Dashboard AI Search
  participant Hook as useBrandDashboard
  participant AIFE as aiApi (FE)
  participant AIB as FastAPI /api/ai
  participant SVC as AIRouter
  participant LLM as Groq Chat Completions

  UI->>Hook: queryAI("Find creators in fitness", brandId)
  Hook->>AIFE: POST /query {query, brand_id}
  AIFE->>AIB: POST /api/ai/query
  AIB->>SVC: process_query(query, brand_id)
  SVC->>LLM: system + user messages
  LLM-->>SVC: JSON-like output
  SVC-->>AIB: Enhanced AIQueryResponse
  AIB-->>AIFE: AIQueryResponse
  AIFE-->>Hook: AIQueryResponse
  Hook-->>UI: render intent/explanation/route
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

enhancement, frontend, backend, documentation

Suggested reviewers

  • chandansgowda

Poem

Ears up, I tap the keys—hip hop, hooray!
New routes burrow deep where the dashboards play.
Groq whispers back with intent so bright,
While brands and creators align just right.
I nibble through metrics, contracts in tow—
A carrot-shaped cursor, ready to go! 🥕✨

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 title succinctly captures the main change by indicating a feature addition for revamping the brand dashboard user interface and integrating AI functionality, and it aligns with the PR’s primary focus on UI and AI enhancements. It is concise, descriptive, and clearly signals the scope of the update to readers scanning history. Though the PR also includes backend and integration work, the core theme remains the brand dashboard revamp. This phrasing avoids generic terms and highlights the developer’s intent.
Docstring Coverage ✅ Passed Docstring coverage is 91.49% 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: 12

🧹 Nitpick comments (1)
Backend/app/models/models.py (1)

40-54: Make brand → brand_profile truly one-to-one

BrandProfile.user uses a plain backref, so user.brand_profile becomes a list. The consuming routes and schemas treat it as a single profile, which will break at runtime (e.g., attribute access) and allows multiple profiles per user. Define the relationship explicitly with uselist=False on the user side and switch to back_populates so both ends stay in sync.

-from sqlalchemy.orm import relationship
+from sqlalchemy.orm import relationship
@@
     brand_payments = relationship(
         "SponsorshipPayment",
         foreign_keys="[SponsorshipPayment.brand_id]",
         back_populates="brand",
     )
+    brand_profile = relationship(
+        "BrandProfile",
+        back_populates="user",
+        uselist=False,
+        cascade="all, delete-orphan",
+    )
@@
-    user = relationship("User", backref="brand_profile")
+    user = relationship("User", back_populates="brand_profile")

Also applies to: 185-241

📜 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 34ede84.

⛔ Files ignored due to path filters (1)
  • Frontend/public/aossielogo.png is excluded by !**/*.png
📒 Files selected for processing (18)
  • 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/schemas/schema.py (2 hunks)
  • Backend/app/services/ai_router.py (1 hunks)
  • Backend/requirements.txt (1 hunks)
  • Backend/sql.txt (1 hunks)
  • Frontend/README-INTEGRATION.md (1 hunks)
  • Frontend/src/components/collaboration-hub/CreatorMatchGrid.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/services/aiApi.ts (1 hunks)
  • Frontend/src/services/brandApi.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
Frontend/src/components/user-nav.tsx (1)
Frontend/src/components/ui/dropdown-menu.tsx (1)
  • DropdownMenuItem (180-180)
Backend/app/models/models.py (2)
Backend/app/routes/post.py (1)
  • generate_uuid (31-32)
Backend/app/models/chat.py (1)
  • generate_uuid (9-10)
Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx (1)
Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx (1)
  • CreatorMatchCard (65-130)
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 (54-64)
  • aiApi (83-83)
Backend/app/services/ai_router.py (1)
Backend/app/routes/ai_query.py (1)
  • get_route_info (83-100)
Backend/app/routes/brand_dashboard.py (3)
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 (19)
  • 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)
  • SponsorshipApplicationResponse (182-194)
  • ApplicationUpdateRequest (196-198)
  • ApplicationSummaryResponse (200-206)
  • PaymentResponse (210-222)
  • PaymentStatusUpdate (224-225)
  • PaymentAnalyticsResponse (227-233)
  • CampaignMetricsUpdate (237-242)
  • SponsorshipCreate (21-27)
Frontend/src/utils/supabase.tsx (1)
  • supabase (11-11)
Frontend/src/pages/Brand/Dashboard.tsx (2)
Frontend/src/hooks/useBrandDashboard.ts (1)
  • useBrandDashboard (6-288)
Frontend/src/services/aiApi.ts (1)
  • queryAI (54-64)
Backend/app/routes/ai_query.py (1)
Backend/app/services/ai_router.py (3)
  • process_query (122-172)
  • list_available_routes (202-204)
  • get_route_info (198-200)
🪛 Ruff (0.13.3)
Backend/app/services/ai_router.py

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

(TRY003)


122-122: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


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

(TRY300)


170-170: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


172-172: 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)


188-188: Abstract raise to an inner function

(TRY301)


190-190: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


205-205: Abstract raise to an inner function

(TRY301)


209-209: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


226-226: Abstract raise to an inner function

(TRY301)


230-230: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


268-268: Abstract raise to an inner function

(TRY301)


272-272: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


310-310: Abstract raise to an inner function

(TRY301)


314-314: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


327-327: Abstract raise to an inner function

(TRY301)


334-334: Abstract raise to an inner function

(TRY301)


338-338: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


351-351: Abstract raise to an inner function

(TRY301)


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

Remove assignment to unused variable response

(F841)


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

(TRY300)


359-359: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


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

(TRY300)


392-392: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


398-398: Unused function argument: brand_id

(ARG001)


399-399: Unused function argument: industry

(ARG001)


401-401: Unused function argument: location

(ARG001)


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

(TRY300)


437-437: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


442-442: Unused function argument: brand_id

(ARG001)


450-450: Abstract raise to an inner function

(TRY301)


465-470: Consider moving this statement to an else block

(TRY300)


474-474: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


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

(TRY300)


523-523: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


554-554: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


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

(TRY300)


571-571: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


597-597: Abstract raise to an inner function

(TRY301)


599-599: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


616-616: Abstract raise to an inner function

(TRY301)


623-623: Abstract raise to an inner function

(TRY301)


627-627: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


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

(TRY300)


683-683: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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)


700-700: Abstract raise to an inner function

(TRY301)


707-707: Abstract raise to an inner function

(TRY301)


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

(TRY300)


726-726: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


747-747: Abstract raise to an inner function

(TRY301)


752-752: Abstract raise to an inner function

(TRY301)


764-764: Abstract raise to an inner function

(TRY301)


768-768: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


810-810: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


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

(TRY300)


855-855: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


871-871: Abstract raise to an inner function

(TRY301)


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

(TRY300)


893-893: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


914-914: Abstract raise to an inner function

(TRY301)


922-922: Abstract raise to an inner function

(TRY301)


926-926: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


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)


1014-1014: Abstract raise to an inner function

(TRY301)


1018-1018: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


1035-1035: Abstract raise to an inner function

(TRY301)


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

(TRY300)


1047-1047: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


1070-1070: Abstract raise to an inner function

(TRY301)


1079-1079: Abstract raise to an inner function

(TRY301)


1083-1083: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


1085-1085: 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

38-38: Abstract raise to an inner function

(TRY301)


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

(TRY300)


63-63: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


78-78: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


90-90: Abstract raise to an inner function

(TRY301)


92-95: Consider moving this statement to an else block

(TRY300)


98-98: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)


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

(B904)


111-115: Consider moving this statement to an else block

(TRY300)


116-116: Do not catch blind exception: Exception

(BLE001)


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

Replace with exception

(TRY400)

Comment on lines +73 to +77
routes = ai_router.list_available_routes()
return {
"available_routes": routes,
"total_routes": len(routes)
}
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

Return the route list in the shape the client expects

ai_router.list_available_routes() yields a dict, but the frontend’s getAvailableRoutes() expects available_routes: string[]. Emit the keys (and optionally pass details separately) so the contract matches.

-        return {
-            "available_routes": routes,
-            "total_routes": len(routes)
-        }
+        return {
+            "available_routes": list(routes.keys()),
+            "total_routes": len(routes),
+            "route_details": routes,
+        }
📝 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
routes = ai_router.list_available_routes()
return {
"available_routes": routes,
"total_routes": len(routes)
}
routes = ai_router.list_available_routes()
return {
"available_routes": list(routes.keys()),
"total_routes": len(routes),
"route_details": routes,
}
🤖 Prompt for AI Agents
In Backend/app/routes/ai_query.py around lines 73 to 77, the handler currently
returns the dict from ai_router.list_available_routes(), but the frontend
expects available_routes to be a list of strings; change the response to emit
available_routes as a list of the dict's keys (e.g., list(routes.keys())) and,
if you need to keep the full dict, return it under a separate field like
route_details so the API contract matches available_routes: string[].

Comment on lines +102 to +115
@router.post("/test")
async def test_ai_query(query: str = Query(..., description="Test query")):
"""
Test endpoint for AI query processing (for development)
"""
try:
# Process test query
result = await ai_router.process_query(query=query)

return {
"test_query": query,
"result": result,
"status": "success"
}
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

Let the test endpoint accept GET (or update the client)

The frontend already issues GET /api/ai/test?query=…; with the current @router.post decorator the call will 405. Either change this route to @router.get or adjust the client before merging—just make sure both sides use the same verb.

🧰 Tools
🪛 Ruff (0.13.3)

111-115: Consider moving this statement to an else block

(TRY300)

🤖 Prompt for AI Agents
In Backend/app/routes/ai_query.py around lines 102 to 115, the test endpoint is
currently decorated with @router.post which causes a 405 when the frontend sends
GET; change the route decorator to @router.get("/test") so it accepts query
string GET requests (no other code changes required since the query param is
already declared), or alternatively coordinate with the frontend to call
POST—prefer updating the decorator to @router.get to match existing client
behavior.

Comment on lines +48 to +153
# 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

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 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)

# Simple in-memory rate limiting (for development)
request_counts = {}

def check_rate_limit(user_id: str, max_requests: int = 100, window_seconds: int = 60):
"""Simple rate limiting check (in production, use Redis)"""
current_time = datetime.now(timezone.utc)
key = f"{user_id}:{current_time.minute}"

if key not in request_counts:
request_counts[key] = 0

request_counts[key] += 1

if request_counts[key] > max_requests:
raise HTTPException(status_code=429, detail="Rate limit exceeded")

return True

# ============================================================================
# DASHBOARD OVERVIEW ROUTES
# ============================================================================

@router.get("/dashboard/overview", response_model=DashboardOverviewResponse)
async def get_dashboard_overview(brand_id: str = Query(..., description="Brand user ID")):
"""
Get dashboard overview with key metrics for a brand
"""
# Validate brand_id format
validate_uuid_format(brand_id, "brand_id")

try:
# Get brand's campaigns
campaigns = safe_supabase_query(
lambda: supabase.table("sponsorships").select("*").eq("brand_id", brand_id).execute(),
"Failed to fetch campaigns"
)

# 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

# Get recent applications (only if campaigns exist)
applications = []
if campaigns:
campaign_ids = [campaign["id"] for campaign in campaigns]
applications = safe_supabase_query(
lambda: supabase.table("sponsorship_applications").select("*").in_("sponsorship_id", campaign_ids).execute(),
"Failed to fetch applications"
)

# Calculate metrics
total_campaigns = len(campaigns)
active_campaigns = len([c for c in campaigns if c.get("status") == "open"])

# Calculate total revenue from completed payments
payments = safe_supabase_query(
lambda: supabase.table("sponsorship_payments").select("*").eq("brand_id", brand_id).eq("status", "completed").execute(),
"Failed to fetch payments"
)
total_revenue = sum(float(payment.get("amount", 0)) for payment in payments)

# Get creator matches
matches = safe_supabase_query(
lambda: supabase.table("creator_matches").select("*").eq("brand_id", brand_id).execute(),
"Failed to fetch creator matches"
)
total_creators_matched = len(matches)

# Recent activity (last 5 applications)
recent_activity = applications[:5] if applications else []

return DashboardOverviewResponse(
total_campaigns=total_campaigns,
active_campaigns=active_campaigns,
total_revenue=total_revenue,
total_creators_matched=total_creators_matched,
recent_activity=recent_activity
)
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

Blocker: brand data exposed via trust-on-query brand_id.

Every route here trusts a caller-supplied brand_id query/body value and never authenticates the requester. A malicious user can enumerate UUIDs and read/modify any brand’s campaigns, payments, contracts, etc. Defining validate_brand_access/require_brand_role doesn’t help because they are never invoked. We need to require an authenticated brand user (e.g., current_user = Depends(get_current_user)), derive brand_id from that session, and reject mismatches before touching Supabase.

Also applies to: 165-362

🧰 Tools
🪛 Ruff (0.13.3)

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)

🤖 Prompt for AI Agents
In Backend/app/routes/brand_dashboard.py around lines 48 to 153 (and similarly
for lines 165-362), the endpoint blindly trusts a caller-supplied brand_id query
parameter and never authenticates the requester; change the handlers to require
an authenticated user via Depends(get_current_user) and derive brand_id from
that session object instead of accepting it from the client, enforce
require_brand_role(current_user.role) and validate_brand_access(current_user.id,
current_user.id) (or simply compare derived brand_id to the session id), remove
or ignore the incoming brand_id parameter, and update all Supabase queries to
use the authenticated brand_id; apply the same pattern to the other routes
referenced (165-362) and ensure any helper calls (validate_uuid_format,
safe_supabase_query) still receive the derived ID and that unauthorized access
returns 403.

Comment on lines +139 to +144
response = self.client.chat.completions.create(
model="llama3-8b-8192", # Fast and cost-effective
messages=messages,
temperature=0.1, # Low temperature for consistent routing
max_tokens=500
)
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

Avoid blocking the event loop with the Groq client

Groq.chat.completions.create() is synchronous; invoking it directly inside an async def will block the FastAPI event loop during every AI call. Run it in a thread (or switch to an async client) so concurrent requests stay responsive.

-from groq import Groq
+from groq import Groq
+import anyio
@@
-            response = self.client.chat.completions.create(
-                model="llama3-8b-8192",  # Fast and cost-effective
-                messages=messages,
-                temperature=0.1,  # Low temperature for consistent routing
-                max_tokens=500
-            )
+            response = await anyio.to_thread.run_sync(
+                lambda: self.client.chat.completions.create(
+                    model="llama3-8b-8192",  # Fast and cost-effective
+                    messages=messages,
+                    temperature=0.1,  # Low temperature for consistent routing
+                    max_tokens=500,
+                )
+            )
📝 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
response = self.client.chat.completions.create(
model="llama3-8b-8192", # Fast and cost-effective
messages=messages,
temperature=0.1, # Low temperature for consistent routing
max_tokens=500
)
from groq import Groq
import anyio
response = await anyio.to_thread.run_sync(
lambda: self.client.chat.completions.create(
model="llama3-8b-8192", # Fast and cost-effective
messages=messages,
temperature=0.1, # Low temperature for consistent routing
max_tokens=500,
)
)
🤖 Prompt for AI Agents
In Backend/app/services/ai_router.py around lines 139 to 144, the synchronous
call to Groq.chat.completions.create() is being invoked directly inside an async
function which will block the FastAPI event loop; change it to run in a thread
(or switch to an async client). Specifically, import asyncio if not present and
replace the direct call with an awaited call that runs the blocking function in
a thread (e.g., await asyncio.to_thread(...) or await loop.run_in_executor(...))
passing the same model/messages/temperature/max_tokens arguments, and preserve
surrounding error handling and return value usage so the async handler remains
non-blocking and exceptions are propagated correctly.

Comment on lines +21 to +22
{currentCreators.map((creator, index) => (
<CreatorMatchCard key={`${creator.name}-${index}`} {...creator} />
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

🧩 Analysis chain

Avoid using array index in React keys.

Using index as part of the key is an anti-pattern that can cause React reconciliation bugs, especially with pagination. When navigating between pages, the index resets (0-3 on each page), so creators with the same name on different pages will have identical keys during their render cycles. This can lead to:

  • Wrong component instances being reused (state not resetting properly)
  • Unnecessary re-renders and performance issues
  • Subtle bugs if the list is filtered, reordered, or dynamically updated

The AI summary indicates the backend provides a CreatorMatch model with a primary key. If creator.id exists and is unique (which it should be), use it directly as the key instead.

Run the following script to verify whether id is available in the creator data:

If id is available, apply this diff:

-        {currentCreators.map((creator, index) => (
-          <CreatorMatchCard key={`${creator.name}-${index}`} {...creator} />
+        {currentCreators.map((creator) => (
+          <CreatorMatchCard key={creator.id} {...creator} />

If id is truly unavailable, this indicates a data structure issue that should be addressed at the API level rather than worked around with composite keys.


🏁 Script executed:

#!/bin/bash
# Description: Check if CreatorMatchCardProps includes an id field and verify backend API response structure

# Check the props interface definition
echo "=== Checking CreatorMatchCardProps interface ==="
ast-grep --pattern $'export interface CreatorMatchCardProps {
  $$$
}'

# Check if id is used elsewhere in creator-related components
echo -e "\n=== Checking for id usage in creator components ==="
rg -n --type=tsx --type=ts -C3 'creator\.id' Frontend/src/components/collaboration-hub/

# Check backend schema for CreatorMatch to confirm id field
echo -e "\n=== Checking backend CreatorMatch schema ==="
rg -n --type=py -C5 'class CreatorMatch'

Length of output: 3313


Avoid index-based React keys; use a stable id instead. Refactor currentCreators and CreatorMatchCardProps to include the API’s id, then replace key={${creator.name}-${index}} with key={creator.id}.

🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx around lines
21-22, the list is using an index-based key (`${creator.name}-${index}`) which
is unstable; update the creator data model and prop types to include the API's
stable `id` field (add `id` to currentCreators items and CreatorMatchCardProps),
then replace the key with `key={creator.id}` on the CreatorMatchCard; ensure all
places that construct or map creators (API parsing, tests, and parent
components) populate `id` and adjust types/imports accordingly.

// AI API Service
// Handles AI-related API calls to the backend

const AI_API_BASE_URL = 'http://localhost:8000/api/ai';
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

Do not pin the AI API base to localhost

The singleton will break everywhere except a developer’s laptop. Pull the base URL from runtime config (with a sane default) so staging/production work.

-const AI_API_BASE_URL = 'http://localhost:8000/api/ai';
+const AI_API_BASE_URL = `${import.meta.env.VITE_API_BASE_URL ?? window.location.origin}/api/ai`;
🤖 Prompt for AI Agents
In Frontend/src/services/aiApi.ts around line 4 the AI API base is hardcoded to
'http://localhost:8000/api/ai' which will break non-local environments; change
the constant initialization to pull the base URL from runtime configuration
(e.g. process.env.AI_API_BASE_URL, import.meta.env.VITE_AI_API_BASE_URL, or a
window/global runtime config like window.__RUNTIME_CONFIG__.AI_API_BASE_URL) and
fall back to a sensible default (e.g. the current localhost URL) so
staging/production can override it at runtime.

Comment on lines +76 to +79
// Test AI query (for development)
async testQuery(query: string): Promise<any> {
return this.makeRequest<any>(`/test?query=${encodeURIComponent(query)}`);
}
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

Align the test endpoint verb with the backend

The backend route is declared as POST /api/ai/test, so this GET call will return 405. Either hit it with POST (body or query string) or change the backend to accept GET—just make sure both sides agree before merging.

🤖 Prompt for AI Agents
In Frontend/src/services/aiApi.ts around lines 76 to 79, the client calls GET
/test but the backend expects POST /api/ai/test; change testQuery to use
this.makeRequest with method 'POST' to the '/test' path and send the query in
the request body (e.g., { query }) with appropriate JSON content-type handling
used by makeRequest so the client and backend verb agree.

// Brand Dashboard API Service
// Handles all API calls to the backend for brand dashboard functionality

const API_BASE_URL = 'http://localhost:8000/api/brand';
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

Stop hard-coding localhost for API base

Shipping with http://localhost will make every deployed build talk to the wrong host (or fail outright) outside local dev. Please drive this from configuration (env/proxy) with a safe fallback to the current origin.

-const API_BASE_URL = 'http://localhost:8000/api/brand';
+const API_BASE_URL = `${import.meta.env.VITE_API_BASE_URL ?? window.location.origin}/api/brand`;
📝 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
const API_BASE_URL = 'http://localhost:8000/api/brand';
const API_BASE_URL = `${import.meta.env.VITE_API_BASE_URL ?? window.location.origin}/api/brand`;
🤖 Prompt for AI Agents
In Frontend/src/services/brandApi.ts around line 4, the API base URL is
hard-coded to 'http://localhost:8000/api/brand'; replace this with a
configurable value driven by environment or runtime config (e.g. read from
process.env.REACT_APP_API_BASE_URL or a config module, falling back to
window.location.origin) and then append the '/api/brand' path as needed so
deployed builds use the correct host; ensure the fallback is safe (use
window.location.origin) and that the code handles trailing slashes consistently.

Comment on lines +172 to +177
const params = new URLSearchParams({ brand_id: brandId });
if (filters?.industry) params.append('industry', filters.industry);
if (filters?.min_engagement) params.append('min_engagement', filters.min_engagement.toString());
if (filters?.location) params.append('location', filters.location);

return this.makeRequest<any[]>(`/creators/search?${params.toString()}`);
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 | 🟡 Minor

Preserve zero values when forwarding min_engagement

A falsy check drops legitimate 0 thresholds. Check for undefined instead so we forward zero safely.

-    if (filters?.min_engagement) params.append('min_engagement', filters.min_engagement.toString());
+    if (filters?.min_engagement !== undefined) {
+      params.append('min_engagement', filters.min_engagement.toString());
+    }
📝 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
const params = new URLSearchParams({ brand_id: brandId });
if (filters?.industry) params.append('industry', filters.industry);
if (filters?.min_engagement) params.append('min_engagement', filters.min_engagement.toString());
if (filters?.location) params.append('location', filters.location);
return this.makeRequest<any[]>(`/creators/search?${params.toString()}`);
const params = new URLSearchParams({ brand_id: brandId });
if (filters?.industry) params.append('industry', filters.industry);
if (filters?.min_engagement !== undefined) {
params.append('min_engagement', filters.min_engagement.toString());
}
if (filters?.location) params.append('location', filters.location);
return this.makeRequest<any[]>(`/creators/search?${params.toString()}`);
🤖 Prompt for AI Agents
In Frontend/src/services/brandApi.ts around lines 172 to 177, the falsy check
for filters?.min_engagement drops legitimate 0 values; change the condition to
explicitly check for undefined (e.g., filters?.min_engagement !== undefined)
before appending, and only call toString when the value is defined so zero is
forwarded correctly in the query params.

Comment on lines +202 to +211
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 | 🟠 Major

Require brandId when building the status update URL

Treating brandId as optional means we hit /applications/{id}?brand_id=undefined, which will 404 and pollute server logs. Make the argument mandatory (or explicitly guard/throw when it is missing).

-    status: string,
-    notes?: string,
-    brandId?: string
+    status: string,
+    notes?: string,
+    brandId: string
   ): Promise<Application> {
     return this.makeRequest<Application>(`/applications/${applicationId}?brand_id=${brandId}`, {
📝 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
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 }),
});
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 }),
});
}
🤖 Prompt for AI Agents
In Frontend/src/services/brandApi.ts around lines 202 to 211, the
updateApplicationStatus function builds the URL with an optional brandId which
can produce /applications/{id}?brand_id=undefined; make brandId mandatory in the
function signature or validate and throw if missing before constructing the URL,
and when building the query param use a proper encoded value (e.g.
encodeURIComponent) to avoid injecting "undefined"; update any callers/types
accordingly so the function is never called without a valid brandId.

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