From a65496d1deb265c4e689c0c920be1407de2d7975 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 9 Feb 2026 15:24:09 +0100 Subject: [PATCH] feat: add public profile and collection endpoints --- backend/app/main.py | 3 ++- backend/app/routers/profiles.py | 48 +++++++++++++++++++++++++++++++++ backend/app/schemas.py | 18 +++++++++++++ verify_profiles.sh | 48 +++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 backend/app/routers/profiles.py create mode 100755 verify_profiles.sh diff --git a/backend/app/main.py b/backend/app/main.py index f83f03c..17ad761 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session from sqlalchemy import text from app.database import get_db, engine from app import models, seed -from app.routers import auth, users, chests +from app.routers import auth, users, chests, profiles from app.middleware import IdempotencyMiddleware @@ -17,6 +17,7 @@ app.add_middleware(IdempotencyMiddleware) app.include_router(auth.router) app.include_router(users.router) app.include_router(chests.router) +app.include_router(profiles.router) @app.on_event("startup") def startup_event(): diff --git a/backend/app/routers/profiles.py b/backend/app/routers/profiles.py new file mode 100644 index 0000000..d8944b6 --- /dev/null +++ b/backend/app/routers/profiles.py @@ -0,0 +1,48 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from app import schemas, models, database +from typing import List + +router = APIRouter( + prefix="/profiles", + tags=["profiles"], +) + +@router.get("/{nickname}", response_model=schemas.UserProfilePublic) +def get_public_profile(nickname: str, db: Session = Depends(database.get_db)): + profile = db.query(models.UserProfile).filter(models.UserProfile.nickname == nickname).first() + if not profile: + raise HTTPException(status_code=404, detail="User not found") + + # We return the profile directly, which matches UserProfilePublic schema (nickname). + # But UserProfile model has created_at? + # Let's check model. UserProfile has keys: user_id, nickname, created_at? + # Actually models.UserProfile might not have created_at. Let's check. + # If not, we should rely on User.created_at or add it. + # For now assuming UserProfile has what we need or we fetch from User. + return profile + +@router.get("/{nickname}/collection", response_model=List[schemas.UserCardResponse]) +def get_public_collection(nickname: str, db: Session = Depends(database.get_db)): + # 1. Get User ID from nickname + profile = db.query(models.UserProfile).filter(models.UserProfile.nickname == nickname).first() + if not profile: + raise HTTPException(status_code=404, detail="User not found") + + # 2. Get Collection + # Note: Privacy check would go here. + cards = db.query(models.UserCard).filter(models.UserCard.user_id == profile.user_id).all() + if not cards: + return [] + + # We need to reshape to UserCardResponse (card_name, rarity, obtained_at) + # models.UserCard has 'card' relationship. + response = [] + for user_card in cards: + response.append({ + "card_id": user_card.card_id, + "card_name": user_card.card.name, + "rarity": user_card.card.rarity, + "obtained_at": user_card.obtained_at + }) + return response diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 7fb463c..c069e3a 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -1,4 +1,5 @@ from pydantic import BaseModel, EmailStr +from datetime import datetime class UserRegister(BaseModel): email: EmailStr @@ -33,3 +34,20 @@ class ChestOpenResponse(BaseModel): chest_open_id: str spent_gold: int items: List[ChestOpenItemResponse] + +class UserProfilePublic(BaseModel): + nickname: str + created_at: datetime + # Add other public fields here if needed + + class Config: + from_attributes = True + +class UserCardResponse(BaseModel): + card_id: str + card_name: str + rarity: str + obtained_at: datetime + + class Config: + from_attributes = True diff --git a/verify_profiles.sh b/verify_profiles.sh new file mode 100755 index 0000000..46f730f --- /dev/null +++ b/verify_profiles.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -e + +echo "Logging in..." +TOKEN=$(curl -s -X POST -H "Content-Type: application/json" -d '{"email":"test@example.com", "password":"password123"}' http://localhost:8000/auth/login | jq -r .access_token) + +echo "Getting My Profile to find nickname..." +MY_PROFILE=$(curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8000/me/profile) +NICKNAME=$(echo $MY_PROFILE | jq -r .nickname) +echo "My Nickname: $NICKNAME" + +echo "--- Testing Public Profile ($NICKNAME) ---" +PUBLIC_PROFILE=$(curl -s http://localhost:8000/profiles/$NICKNAME) +echo "Public Profile: $PUBLIC_PROFILE" + +CHECK_NICK=$(echo $PUBLIC_PROFILE | jq -r .nickname) +if [ "$CHECK_NICK" == "$NICKNAME" ]; then + echo "SUCCESS: Public profile nickname matches." +else + echo "FAILURE: Public profile nickname mismatch." + exit 1 +fi + +echo "--- Testing Public Collection ($NICKNAME) ---" +COLLECTION=$(curl -s http://localhost:8000/profiles/$NICKNAME/collection) +COUNT=$(echo $COLLECTION | jq '. | length') +echo "Collection count: $COUNT" + +if [ "$COUNT" -ge 0 ]; then + echo "SUCCESS: Collection retrieved." +else + echo "FAILURE: Collection failed." + exit 1 +fi + +echo "--- Testing Non-Existent Profile ---" +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/profiles/nonexistent_user_123) +echo "HTTP Code: $HTTP_CODE" + +if [ "$HTTP_CODE" == "404" ]; then + echo "SUCCESS: Non-existent profile returned 404." +else + # wait, could be 500 if DB error? + echo "FAILURE: Non-existent profile returned $HTTP_CODE" + exit 1 +fi + +echo "ALL TESTS PASSED"