Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Backend/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os
from dotenv import load_dotenv

load_dotenv()

class Settings:
SECRET_KEY: str = os.getenv("SECRET_KEY", "your-super-secret-key-change-me")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 # 24 hours

settings = Settings()
2 changes: 2 additions & 0 deletions Backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .routes.chat import router as chat_router
from .routes.match import router as match_router
from sqlalchemy.exc import SQLAlchemyError
from .routes import auth
import logging
import os
from dotenv import load_dotenv
Expand Down Expand Up @@ -56,6 +57,7 @@ async def lifespan(app: FastAPI):
app.include_router(match_router)
app.include_router(ai.router)
app.include_router(ai.youtube_router)
app.include_router(auth.router)


@app.get("/")
Expand Down
33 changes: 19 additions & 14 deletions Backend/app/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,22 @@ class User(Base):
__tablename__ = "users"

id = Column(String, primary_key=True, default=generate_uuid)
username = Column(String, unique=True, nullable=False)
email = Column(String, unique=True, nullable=False)
# password_hash = Column(Text, nullable=False) # Removed as Supabase handles auth
role = Column(String, nullable=False) # 'creator' or 'brand'
# Added index=True to username and email for faster login lookups
username = Column(String, unique=True, index=True, nullable=False)
email = Column(String, unique=True, index=True, nullable=False)

# Restored hashed_password for custom JWT authentication
hashed_password = Column(String, nullable=False)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

role = Column(String, nullable=False, default="creator") # 'creator' or 'brand'
profile_image = Column(Text, nullable=True)
bio = Column(Text, nullable=True)
created_at = Column(TIMESTAMP, default=datetime.utcnow)

is_online = Column(Boolean, default=False) # ✅ Track if user is online
is_online = Column(Boolean, default=False)
last_seen = Column(TIMESTAMP, default=datetime.utcnow)

# Existing Relationships
audience = relationship("AudienceInsights", back_populates="user", uselist=False)
sponsorships = relationship("Sponsorship", back_populates="brand")
posts = relationship("UserPost", back_populates="user")
Expand Down Expand Up @@ -66,7 +71,7 @@ class AudienceInsights(Base):
time_of_attention = Column(Integer) # in seconds
price_expectation = Column(DECIMAL(10, 2))
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)
Comment on lines 73 to 75
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

DateTime(timezone=True) paired with datetime.utcnow produces naive datetimes — use datetime.now(timezone.utc) across all updated timestamp columns.

datetime.utcnow() returns a naive datetime (no tzinfo). Pairing it with DateTime(timezone=True) means the default value inserted into the DB lacks timezone information, which is inconsistent with the declared column type and causes silent data integrity issues on timezone-aware DB backends (e.g. PostgreSQL TIMESTAMPTZ). This affects all the updated DateTime(timezone=True) columns: AudienceInsights.created_at, Sponsorship.created_at, UserPost.created_at, SponsorshipApplication.applied_at, Collaboration.created_at, and SponsorshipPayment.transaction_date.

Additionally, datetime.utcnow is deprecated as of Python 3.12.

♻️ Proposed fix (representative — apply to all 6 columns)
-from datetime import datetime
+from datetime import datetime, timezone
 ...
-        DateTime(timezone=True), default=datetime.utcnow
+        DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/models/models.py` around lines 73 - 75, Change the naive UTC
defaults to timezone-aware datetimes for all DateTime(timezone=True) timestamp
columns: replace uses of datetime.utcnow (or datetime.utcnow()) with
datetime.now(timezone.utc) as the default factory for
AudienceInsights.created_at, Sponsorship.created_at, UserPost.created_at,
SponsorshipApplication.applied_at, Collaboration.created_at, and
SponsorshipPayment.transaction_date; ensure you import timezone from datetime
and apply the same change consistently across those column definitions so the DB
receives tz-aware timestamps.


user = relationship("User", back_populates="audience")
Expand All @@ -80,12 +85,12 @@ class Sponsorship(Base):
brand_id = Column(String, ForeignKey("users.id"), nullable=False)
title = Column(String, nullable=False)
description = Column(Text, nullable=False)
required_audience = Column(JSON) # {"age": ["18-24"], "location": ["USA", "UK"]}
required_audience = Column(JSON)
budget = Column(DECIMAL(10, 2))
engagement_minimum = Column(Float)
status = Column(String, default="open")
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

brand = relationship("User", back_populates="sponsorships")
Expand All @@ -102,9 +107,9 @@ class UserPost(Base):
content = Column(Text, nullable=False)
post_url = Column(Text, nullable=True)
category = Column(String, nullable=True)
engagement_metrics = Column(JSON) # {"likes": 500, "comments": 100, "shares": 50}
engagement_metrics = Column(JSON)
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

user = relationship("User", back_populates="posts")
Expand All @@ -121,7 +126,7 @@ class SponsorshipApplication(Base):
proposal = Column(Text, nullable=False)
status = Column(String, default="pending")
applied_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

creator = relationship("User", back_populates="applications")
Expand All @@ -138,7 +143,7 @@ class Collaboration(Base):
collaboration_details = Column(Text, nullable=False)
status = Column(String, default="pending")
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)


Expand All @@ -153,10 +158,10 @@ class SponsorshipPayment(Base):
amount = Column(DECIMAL(10, 2), nullable=False)
status = Column(String, default="pending")
transaction_date = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
DateTime(timezone=True), default=datetime.utcnow
)

creator = relationship("User", foreign_keys=[creator_id], back_populates="payments")
brand = relationship(
"User", foreign_keys=[brand_id], back_populates="brand_payments"
)
)
Empty file removed Backend/app/models/users.py
Empty file.
63 changes: 58 additions & 5 deletions Backend/app/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,60 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from ..db.db import get_db
from ..models.models import User
from ..services.auth_service import hash_password, verify_password, create_access_token
from pydantic import BaseModel, EmailStr
from ..services.get_current_user import get_current_user

router = APIRouter()
router = APIRouter(prefix="/auth", tags=["Authentication"])

@router.get("/auth/ping")
def ping():
return {"message": "Auth route is working!"}
# Pydantic schemas for data validation
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
Comment on lines +13 to +16
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

UserCreate has no role field — all users silently default to "creator", locking out brand signups.

The User model supports "creator" and "brand" roles, but there is no way for a brand to register through this endpoint. If brand-specific signup is intentional (e.g. a separate flow), document it; otherwise add the field.

♻️ Proposed fix
 class UserCreate(BaseModel):
     username: str
     email: EmailStr
     password: str
+    role: str = "creator"  # 'creator' or 'brand'

And in the signup handler:

     new_user = User(
         username=user_data.username,
         email=user_data.email,
         hashed_password=hash_password(user_data.password),
+        role=user_data.role,
     )
📝 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
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
role: str = "creator" # 'creator' or 'brand'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/routes/auth.py` around lines 13 - 16, UserCreate currently lacks
a role field so all signups default to "creator"; add a role attribute to the
UserCreate Pydantic model (e.g., role: Optional[Literal["creator","brand"]] =
"creator" or a required Literal if you want explicit choice) and update the
signup handler (the function that consumes UserCreate and creates the User
record) to pass this role into user creation; also validate allowed values
(Literal or Enum) to prevent invalid inputs and adjust any downstream logic or
docs if brand signups should be restricted to a separate flow.


class UserLogin(BaseModel):
email: EmailStr
password: str

@router.post("/signup")
async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
# Check if user already exists
result = await db.execute(select(User).where(User.email == user_data.email))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Email already registered")

new_user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hash_password(user_data.password)
)
db.add(new_user)
await db.commit()
await db.refresh(new_user)
return {"message": "User created successfully"}
Comment on lines +22 to +37
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Signup silently raises a 500 on duplicate username — check uniqueness before inserting.

The endpoint checks for duplicate email (line 25) but not username. Since username is declared unique=True in the User model, submitting a duplicate username will surface as an unhandled IntegrityError (HTTP 500) rather than a clean HTTP 400.

🐛 Proposed fix
 `@router.post`("/signup")
 async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
     # Check if user already exists
     result = await db.execute(select(User).where(User.email == user_data.email))
     if result.scalars().first():
         raise HTTPException(status_code=400, detail="Email already registered")

+    result = await db.execute(select(User).where(User.username == user_data.username))
+    if result.scalars().first():
+        raise HTTPException(status_code=400, detail="Username already taken")
+
     new_user = User(
📝 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
@router.post("/signup")
async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
# Check if user already exists
result = await db.execute(select(User).where(User.email == user_data.email))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Email already registered")
new_user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hash_password(user_data.password)
)
db.add(new_user)
await db.commit()
await db.refresh(new_user)
return {"message": "User created successfully"}
`@router.post`("/signup")
async def signup(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
# Check if user already exists
result = await db.execute(select(User).where(User.email == user_data.email))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Email already registered")
result = await db.execute(select(User).where(User.username == user_data.username))
if result.scalars().first():
raise HTTPException(status_code=400, detail="Username already taken")
new_user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hash_password(user_data.password)
)
db.add(new_user)
await db.commit()
await db.refresh(new_user)
return {"message": "User created successfully"}
🧰 Tools
🪛 Ruff (0.15.1)

[warning] 23-23: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/routes/auth.py` around lines 22 - 37, The signup endpoint
currently only checks email uniqueness and can raise an unhandled IntegrityError
for duplicate usernames; update the signup function to explicitly check for
existing username by querying select(User).where(User.username ==
user_data.username) and raise HTTPException(status_code=400, detail="Username
already registered") before creating the User, and additionally wrap the
db.commit()/db.refresh(new_user) in a try/except catching
sqlalchemy.exc.IntegrityError to convert any remaining constraint violations
into HTTP 400 errors for robustness.


@router.post("/login")
async def login(credentials: UserLogin, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).where(User.email == credentials.email))
user = result.scalars().first()

if not user or not verify_password(credentials.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password"
)

access_token = create_access_token(data={"sub": str(user.id)})
return {"access_token": access_token, "token_type": "bearer"}

@router.get("/me")
async def get_me(current_user: User = Depends(get_current_user)):
return {
"id": str(current_user.id),
"username": current_user.username,
"email": current_user.email,
"role": current_user.role
}
18 changes: 18 additions & 0 deletions Backend/app/services/auth_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from datetime import datetime, timedelta
from jose import jwt
from passlib.context import CryptContext
from ..config import settings

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str):
return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str):
return pwd_context.verify(plain_password, hashed_password)

def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
37 changes: 37 additions & 0 deletions Backend/app/services/get_current_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
Copy link
Copy Markdown
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

🌐 Web query:

SQLAlchemy 2.0 sqlalchemy.future deprecated removed

💡 Result:

In SQLAlchemy 1.4, sqlalchemy.future was a transitional package (e.g., from sqlalchemy.future import select, sqlalchemy.future.Engine/Connection) used to run in “2.0 mode” while still on 1.4. (docs.sqlalchemy.org)

In SQLAlchemy 2.0, those “future” APIs are effectively the default way of working, so you should stop importing from sqlalchemy.future and import from the normal locations instead (e.g., from sqlalchemy import select, create_engine). The SQLAlchemy 2.0 docs describe the old “future mode” page as a 1.4→2.0 transition artifact. (docs.sqlalchemy.org)

Also in 2.0, the future= parameter on create_engine() / Session is kept only for backwards compatibility; if you specify it, it must be True (so you can usually just remove it). (docs.sqlalchemy.org)

Citations:


🏁 Script executed:

# Check if the files contain the imports mentioned
find Backend -name "get_current_user.py" -o -name "auth.py" | head -20

Repository: AOSSIE-Org/InPactAI

Length of output: 130


🏁 Script executed:

# Find and check the requirements/setup files for SQLAlchemy version
fd -e txt -e cfg -e toml -e ini -e lock | xargs grep -l -i "sqlalchemy" 2>/dev/null | head -10

Repository: AOSSIE-Org/InPactAI

Length of output: 87


🏁 Script executed:

# Check the imports in both files
cat -n Backend/app/services/get_current_user.py | head -10

Repository: AOSSIE-Org/InPactAI

Length of output: 513


🏁 Script executed:

cat -n Backend/app/routes/auth.py | head -10

Repository: AOSSIE-Org/InPactAI

Length of output: 586


🏁 Script executed:

# Check SQLAlchemy version in requirements
cat Backend/requirements.txt | grep -i sqlalchemy

Repository: AOSSIE-Org/InPactAI

Length of output: 81


Replace from sqlalchemy.future import select with from sqlalchemy import select.

sqlalchemy.future was a forward-compatibility shim in SQLAlchemy 1.4. In SQLAlchemy 2.0 (in use here: SQLAlchemy==2.0.40), the 2.0-style select() is the standard API, and imports should come directly from sqlalchemy. The same fix applies to Backend/app/routes/auth.py line 3.

♻️ Fix
-from sqlalchemy.future import select
+from sqlalchemy import select
📝 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
from sqlalchemy.future import select
from sqlalchemy import select
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/services/get_current_user.py` at line 5, Replace the deprecated
import "from sqlalchemy.future import select" with the SQLAlchemy 2.0 import
"from sqlalchemy import select" in the modules that use it (e.g.,
get_current_user.py and auth.py); locate the top-level import statement that
references sqlalchemy.future and change it to import select directly from
sqlalchemy so the code uses the 2.0-style API.

from ..db.db import get_db
from ..models.models import User
from ..config import settings

# This tells FastAPI where to look for the token (the /auth/login route)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OAuth2PasswordBearer(tokenUrl="auth/login") points to a JSON-based endpoint — Swagger "Authorize" will not work.

OAuth2PasswordBearer expects the tokenUrl endpoint to accept application/x-www-form-urlencoded (OAuth2 standard with username/password form fields). The /auth/login endpoint instead accepts a JSON body (UserLogin). The token URL in the scheme is only used for Swagger UI's "Authorize" dialog, so this won't cause a runtime auth failure, but the interactive docs will be broken and misleading for API consumers.

Either change /auth/login to accept OAuth2PasswordRequestForm (form data), or use a plain HTTPBearer scheme and document the JSON login contract separately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/app/services/get_current_user.py` at line 11, The OAuth2 scheme
declaration oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login") points
to /auth/login which accepts JSON, breaking Swagger's "Authorize"; fix by either
(A) changing the /auth/login endpoint handler to accept an
OAuth2PasswordRequestForm (replace JSON UserLogin input with
OAuth2PasswordRequestForm and extract username/password to issue the token) so
Swagger form-based auth works, or (B) if you must keep JSON, replace the scheme
with an HTTPBearer scheme (e.g., use fastapi.security.HTTPBearer) and document
the JSON login contract separately; update any uses of oauth2_scheme
accordingly.


async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db)
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# Decode the JWT token
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception

# Fetch the user from the DB
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalars().first()

if user is None:
raise credentials_exception
return user
3 changes: 3 additions & 0 deletions Backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ urllib3==2.3.0
uvicorn==0.34.0
websockets==14.2
yarl==1.18.3
passlib[bcrypt]==1.7.4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
python-jose[cryptography]==3.3.0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
email-validator==2.1.0.post1