Phase 3.1: Add remaining local files
This commit is contained in:
@@ -19,6 +19,7 @@ router = APIRouter()
|
||||
|
||||
def enrich_completion_log(db: Session, log: ChoreCompletionLog) -> dict:
|
||||
"""Add related information to completion log."""
|
||||
<<<<<<< HEAD
|
||||
# Get chore info
|
||||
chore = db.query(Chore).filter(Chore.id == log.chore_id).first()
|
||||
|
||||
@@ -26,6 +27,11 @@ def enrich_completion_log(db: Session, log: ChoreCompletionLog) -> dict:
|
||||
user = db.query(User).filter(User.id == log.user_id).first()
|
||||
|
||||
# Get verified_by info if exists
|
||||
=======
|
||||
chore = db.query(Chore).filter(Chore.id == log.chore_id).first()
|
||||
user = db.query(User).filter(User.id == log.user_id).first()
|
||||
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
verified_by_name = None
|
||||
if log.verified_by_user_id:
|
||||
verified_by = db.query(User).filter(User.id == log.verified_by_user_id).first()
|
||||
@@ -48,6 +54,7 @@ def enrich_completion_log(db: Session, log: ChoreCompletionLog) -> dict:
|
||||
|
||||
|
||||
@router.post("/{chore_id}/complete", response_model=log_schemas.ChoreCompletionLog, status_code=status.HTTP_201_CREATED)
|
||||
<<<<<<< HEAD
|
||||
def complete_chore(
|
||||
chore_id: int,
|
||||
notes: Optional[str] = None,
|
||||
@@ -98,17 +105,37 @@ def complete_chore(
|
||||
ChoreAssignment.chore_id == chore_id
|
||||
).all()
|
||||
|
||||
=======
|
||||
def complete_chore(chore_id: int, notes: Optional[str] = None, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
chore = db.query(Chore).filter(Chore.id == chore_id).first()
|
||||
if not chore:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Chore not found")
|
||||
|
||||
assignment = db.query(ChoreAssignment).filter(ChoreAssignment.chore_id == chore_id, ChoreAssignment.user_id == current_user.id).first()
|
||||
if not assignment:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You are not assigned to this chore")
|
||||
|
||||
completion_log = ChoreCompletionLog(chore_id=chore_id, user_id=current_user.id, completed_at=datetime.utcnow(), notes=notes)
|
||||
db.add(completion_log)
|
||||
assignment.completed_at = datetime.utcnow()
|
||||
|
||||
all_assignments = db.query(ChoreAssignment).filter(ChoreAssignment.chore_id == chore_id).all()
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
if all(a.completed_at is not None for a in all_assignments):
|
||||
chore.completed_at = datetime.utcnow()
|
||||
chore.status = "completed"
|
||||
|
||||
db.commit()
|
||||
db.refresh(completion_log)
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
return enrich_completion_log(db, completion_log)
|
||||
|
||||
|
||||
@router.get("/completions", response_model=List[log_schemas.ChoreCompletionLog])
|
||||
<<<<<<< HEAD
|
||||
def get_completion_logs(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -148,10 +175,21 @@ def get_completion_logs(
|
||||
logs = query.offset(skip).limit(limit).all()
|
||||
|
||||
# Enrich with related data
|
||||
=======
|
||||
def get_completion_logs(skip: int = 0, limit: int = 100, chore_id: Optional[int] = Query(None), user_id: Optional[int] = Query(None), start_date: Optional[datetime] = Query(None), end_date: Optional[datetime] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
query = db.query(ChoreCompletionLog)
|
||||
if chore_id: query = query.filter(ChoreCompletionLog.chore_id == chore_id)
|
||||
if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id)
|
||||
if start_date: query = query.filter(ChoreCompletionLog.completed_at >= start_date)
|
||||
if end_date: query = query.filter(ChoreCompletionLog.completed_at <= end_date)
|
||||
query = query.order_by(ChoreCompletionLog.completed_at.desc())
|
||||
logs = query.offset(skip).limit(limit).all()
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
return [enrich_completion_log(db, log) for log in logs]
|
||||
|
||||
|
||||
@router.get("/reports/weekly", response_model=log_schemas.WeeklyChoreReport)
|
||||
<<<<<<< HEAD
|
||||
def get_weekly_report(
|
||||
user_id: Optional[int] = Query(None, description="Get report for specific user (omit for family-wide)"),
|
||||
weeks_ago: int = Query(0, description="Number of weeks ago (0 = current week)"),
|
||||
@@ -173,10 +211,14 @@ def get_weekly_report(
|
||||
- Recent completions
|
||||
"""
|
||||
# Calculate week boundaries
|
||||
=======
|
||||
def get_weekly_report(user_id: Optional[int] = Query(None), weeks_ago: int = Query(0), db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_of_week = today - timedelta(days=today.weekday()) - timedelta(weeks=weeks_ago)
|
||||
end_of_week = start_of_week + timedelta(days=7)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Base query
|
||||
query = db.query(ChoreCompletionLog).filter(
|
||||
and_(
|
||||
@@ -195,6 +237,12 @@ def get_weekly_report(
|
||||
total_completions = len(logs)
|
||||
|
||||
# Completions by user
|
||||
=======
|
||||
query = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.completed_at >= start_of_week, ChoreCompletionLog.completed_at < end_of_week))
|
||||
if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id)
|
||||
logs = query.all()
|
||||
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
completions_by_user = {}
|
||||
for log in logs:
|
||||
user = db.query(User).filter(User.id == log.user_id).first()
|
||||
@@ -202,6 +250,7 @@ def get_weekly_report(
|
||||
username = user.full_name or user.username
|
||||
completions_by_user[username] = completions_by_user.get(username, 0) + 1
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Completions by chore
|
||||
completions_by_chore = {}
|
||||
for log in logs:
|
||||
@@ -210,11 +259,19 @@ def get_weekly_report(
|
||||
completions_by_chore[chore.title] = completions_by_chore.get(chore.title, 0) + 1
|
||||
|
||||
# Completions by day
|
||||
=======
|
||||
completions_by_chore = {}
|
||||
for log in logs:
|
||||
chore = db.query(Chore).filter(Chore.id == log.chore_id).first()
|
||||
if chore: completions_by_chore[chore.title] = completions_by_chore.get(chore.title, 0) + 1
|
||||
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
completions_by_day = {}
|
||||
for log in logs:
|
||||
day_name = log.completed_at.strftime("%A")
|
||||
completions_by_day[day_name] = completions_by_day.get(day_name, 0) + 1
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Top performers
|
||||
user_stats = []
|
||||
for user_name, count in completions_by_user.items():
|
||||
@@ -360,12 +417,59 @@ def delete_completion_log(
|
||||
detail="Not authorized to delete this completion log"
|
||||
)
|
||||
|
||||
=======
|
||||
user_stats = []
|
||||
for user_name, count in completions_by_user.items():
|
||||
user = db.query(User).filter((User.full_name == user_name) | (User.username == user_name)).first()
|
||||
user_stats.append({"username": user_name, "count": count, "avatar_url": user.avatar_url if user else None})
|
||||
user_stats.sort(key=lambda x: x["count"], reverse=True)
|
||||
top_performers = user_stats[:5]
|
||||
|
||||
recent_logs = sorted(logs, key=lambda x: x.completed_at, reverse=True)[:10]
|
||||
recent_completions = [enrich_completion_log(db, log) for log in recent_logs]
|
||||
|
||||
return {"start_date": start_of_week, "end_date": end_of_week, "total_completions": len(logs), "completions_by_user": completions_by_user, "completions_by_chore": completions_by_chore, "completions_by_day": completions_by_day, "top_performers": top_performers, "recent_completions": recent_completions}
|
||||
|
||||
|
||||
@router.get("/reports/user/{user_id}", response_model=log_schemas.UserChoreStats)
|
||||
def get_user_stats(user_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
|
||||
total_completions = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.user_id == user_id).count()
|
||||
|
||||
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_of_week = today - timedelta(days=today.weekday())
|
||||
completions_this_week = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_week)).count()
|
||||
|
||||
start_of_month = today.replace(day=1)
|
||||
completions_this_month = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_month)).count()
|
||||
|
||||
favorite_chore = None
|
||||
chore_counts = db.query(ChoreCompletionLog.chore_id, func.count(ChoreCompletionLog.id).label('count')).filter(ChoreCompletionLog.user_id == user_id).group_by(ChoreCompletionLog.chore_id).order_by(func.count(ChoreCompletionLog.id).desc()).first()
|
||||
if chore_counts:
|
||||
chore = db.query(Chore).filter(Chore.id == chore_counts[0]).first()
|
||||
if chore: favorite_chore = chore.title
|
||||
|
||||
recent_logs = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.user_id == user_id).order_by(ChoreCompletionLog.completed_at.desc()).limit(10).all()
|
||||
recent_completions = [enrich_completion_log(db, log) for log in recent_logs]
|
||||
|
||||
return {"user_id": user.id, "username": user.username, "full_name": user.full_name, "avatar_url": user.avatar_url, "total_completions": total_completions, "completions_this_week": completions_this_week, "completions_this_month": completions_this_month, "favorite_chore": favorite_chore, "recent_completions": recent_completions}
|
||||
|
||||
|
||||
@router.delete("/completions/{log_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_completion_log(log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first()
|
||||
if not log: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found")
|
||||
if not current_user.is_admin and log.user_id != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
db.delete(log)
|
||||
db.commit()
|
||||
return None
|
||||
|
||||
|
||||
@router.post("/completions/{log_id}/verify", response_model=log_schemas.ChoreCompletionLog)
|
||||
<<<<<<< HEAD
|
||||
def verify_completion(
|
||||
log_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
@@ -394,4 +498,13 @@ def verify_completion(
|
||||
db.commit()
|
||||
db.refresh(log)
|
||||
|
||||
=======
|
||||
def verify_completion(log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first()
|
||||
if not log: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found")
|
||||
if log.user_id == current_user.id: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot verify own completion")
|
||||
log.verified_by_user_id = current_user.id
|
||||
db.commit()
|
||||
db.refresh(log)
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
return enrich_completion_log(db, log)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
"""Application configuration."""
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import List
|
||||
@@ -40,3 +41,6 @@ class Settings(BaseSettings):
|
||||
return self.ALLOWED_ORIGINS
|
||||
|
||||
settings = Settings()
|
||||
=======
|
||||
IiIiQXBwbGljYXRpb24gY29uZmlndXJhdGlvbi4iIiIKZnJvbSBweWRhbnRpY19zZXR0aW5ncyBpbXBvcnQgQmFzZVNldHRpbmdzCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CgoKY2xhc3MgU2V0dGluZ3MoQmFzZVNldHRpbmdzKToKICAgICIiIkFwcGxpY2F0aW9uIHNldHRpbmdzLiIiIgogICAgCiAgICBBUFBfTkFNRTogc3RyID0gIkZhbWlseSBIdWIiCiAgICBBUFBfVkVSU0lPTjogc3RyID0gIjAuMS4wIgogICAgREVCVUc6IGJvb2wgPSBUcnVlCiAgICAKICAgICMgRGF0YWJhc2UKICAgIERBVEFCQVNFX1VSTDogc3RyID0gInNxbGl0ZTovLy8uL2ZhbWlseV9odWIuZGIiCiAgICAKICAgICMgU2VjdXJpdHkKICAgIFNFQ1JFVF9LRVk6IHN0ciA9ICJ5b3VyLXNlY3JldC1rZXktY2hhbmdlLXRoaXMtaW4tcHJvZHVjdGlvbiIKICAgIEFMR09SSVRITTogc3RyID0gIkhTMjU2IgogICAgQUNDRVNTX1RPS0VOX0VYUElSRV9NSU5VVEVTOiBpbnQgPSAzMAogICAgCiAgICAjIEVudmlyb25tZW50CiAgICBFTlZJUk9OTUVOVDogc3RyID0gImRldmVsb3BtZW50IgogICAgCiAgICAjIENPUlMgLSBhY2NlcHRzIGVpdGhlciBjb21tYS1zZXBhcmF0ZWQgc3RyaW5nIG9yIEpTT04gYXJyYXkKICAgIENPUlNfT1JJR0lOUzogc3RyID0gImh0dHA6Ly9sb2NhbGhvc3Q6NTE3MyxodHRwOi8vbG9jYWxob3N0OjMwMDAsaHR0cDovLzEwLjAuMC4xMjc6NTE3MyIKICAgIAogICAgY2xhc3MgQ29uZmlnOgogICAgICAgIGVudl9maWxlID0gIi5lbnYiCiAgICAgICAgY2FzZV9zZW5zaXRpdmUgPSBUcnVlCiAgICAKICAgIEBwcm9wZXJ0eQogICAgZGVmIGNvcnNfb3JpZ2lucyhzZWxmKSAtPiBMaXN0W3N0cl06CiAgICAgICAgIiIiUGFyc2UgQ09SU19PUklHSU5TIGludG8gYSBsaXN0LiIiIgogICAgICAgIGlmIGlzaW5zdGFuY2Uoc2VsZi5DT1JTX09SSUdJTlMsIHN0cik6CiAgICAgICAgICAgIHJldHVybiBbb3JpZ2luLnN0cmlwKCkgZm9yIG9yaWdpbiBpbiBzZWxmLkNPUlNfT1JJR0lOUy5zcGxpdCgnLCcpXQogICAgICAgIHJldHVybiBzZWxmLkNPUlNfT1JJR0lOUwoKCnNldHRpbmdzID0gU2V0dGluZ3MoKQo=
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
"""Main FastAPI application."""
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
<<<<<<< HEAD
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pathlib import Path
|
||||
from app.core.config import settings
|
||||
from app.api.v1 import auth, users, chores, uploads, public, chore_logs
|
||||
=======
|
||||
from app.core.config import settings
|
||||
from app.api.v1 import auth, users, chores
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
# Create FastAPI app
|
||||
app = FastAPI(
|
||||
@@ -15,12 +20,15 @@ app = FastAPI(
|
||||
)
|
||||
|
||||
# Configure CORS
|
||||
<<<<<<< HEAD
|
||||
print("="*70)
|
||||
print("FAMILY HUB - CORS CONFIGURATION")
|
||||
print("="*70)
|
||||
print(f"Allowed Origins: {settings.cors_origins}")
|
||||
print("="*70)
|
||||
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_origins,
|
||||
@@ -29,18 +37,24 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Mount static files for uploads
|
||||
static_path = Path(__file__).parent / "static"
|
||||
static_path.mkdir(exist_ok=True)
|
||||
app.mount("/static", StaticFiles(directory=str(static_path)), name="static")
|
||||
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
# Include routers
|
||||
app.include_router(auth.router, prefix="/api/v1/auth", tags=["authentication"])
|
||||
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
|
||||
app.include_router(chores.router, prefix="/api/v1/chores", tags=["chores"])
|
||||
<<<<<<< HEAD
|
||||
app.include_router(chore_logs.router, prefix="/api/v1/chores", tags=["chore-logs"])
|
||||
app.include_router(uploads.router, prefix="/api/v1/uploads", tags=["uploads"])
|
||||
app.include_router(public.router, prefix="/api/v1/public", tags=["public"])
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Models package
|
||||
from app.models.user import User
|
||||
from app.models.chore import Chore
|
||||
<<<<<<< HEAD
|
||||
from app.models.chore_assignment import ChoreAssignment
|
||||
from app.models.chore_completion_log import ChoreCompletionLog
|
||||
|
||||
__all__ = ["User", "Chore", "ChoreAssignment", "ChoreCompletionLog"]
|
||||
__all__ = ["User", "Chore", "ChoreAssignment", "ChoreCompletionLog"]
|
||||
=======
|
||||
|
||||
__all__ = ["User", "Chore"]
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
@@ -33,11 +33,19 @@ class Chore(Base):
|
||||
title = Column(String(200), nullable=False)
|
||||
description = Column(String(500))
|
||||
room = Column(String(50)) # bedroom1, bedroom2, kitchen, bathroom1, etc.
|
||||
<<<<<<< HEAD
|
||||
frequency = Column(SQLEnum(ChoreFrequency, values_callable=lambda x: [e.value for e in x]), nullable=False)
|
||||
points = Column(Integer, default=0) # Points awarded for completing the chore
|
||||
image_url = Column(String(500)) # URL to chore image
|
||||
assignment_type = Column(SQLEnum(ChoreAssignmentType, values_callable=lambda x: [e.value for e in x]), default=ChoreAssignmentType.ANY_ONE) # How chore should be completed
|
||||
status = Column(SQLEnum(ChoreStatus, values_callable=lambda x: [e.value for e in x]), default=ChoreStatus.PENDING)
|
||||
=======
|
||||
frequency = Column(SQLEnum(ChoreFrequency), nullable=False)
|
||||
points = Column(Integer, default=0) # Points awarded for completing the chore
|
||||
image_url = Column(String(500)) # URL to chore image
|
||||
assignment_type = Column(SQLEnum(ChoreAssignmentType), default=ChoreAssignmentType.ANY_ONE) # How chore should be completed
|
||||
status = Column(SQLEnum(ChoreStatus), default=ChoreStatus.PENDING)
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
assigned_user_id = Column(Integer, ForeignKey("users.id")) # Deprecated - use assignments instead
|
||||
due_date = Column(DateTime)
|
||||
completed_at = Column(DateTime)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""User model."""
|
||||
<<<<<<< HEAD
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime, Date
|
||||
=======
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from app.core.database import Base
|
||||
@@ -15,8 +19,11 @@ class User(Base):
|
||||
hashed_password = Column(String(200), nullable=False)
|
||||
discord_id = Column(String(100)) # For Discord integration
|
||||
profile_picture = Column(String(500)) # URL to profile picture
|
||||
<<<<<<< HEAD
|
||||
avatar_url = Column(String(500)) # URL to uploaded avatar
|
||||
birthday = Column(Date, nullable=True) # Birthday for chore logic
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_admin = Column(Boolean, default=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
@@ -24,5 +31,8 @@ class User(Base):
|
||||
|
||||
# Relationships (lazy loaded to avoid circular imports)
|
||||
chores = relationship("Chore", back_populates="assigned_user", lazy="select")
|
||||
<<<<<<< HEAD
|
||||
chore_assignments = relationship("ChoreAssignment", back_populates="user", lazy="select")
|
||||
chore_completion_logs = relationship("ChoreCompletionLog", foreign_keys="[ChoreCompletionLog.user_id]", back_populates="user", lazy="select")
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Schemas package
|
||||
<<<<<<< HEAD
|
||||
from app.schemas import auth, chore, user, chore_completion_log
|
||||
|
||||
__all__ = ["auth", "chore", "user", "chore_completion_log"]
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
"""Chore schemas."""
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
<<<<<<< HEAD
|
||||
from typing import Optional, Union, List
|
||||
from datetime import datetime, date
|
||||
|
||||
from app.models.chore import ChoreFrequency, ChoreStatus, ChoreAssignmentType
|
||||
=======
|
||||
from typing import Optional, Union
|
||||
from datetime import datetime, date
|
||||
|
||||
from app.models.chore import ChoreFrequency, ChoreStatus
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class ChoreBase(BaseModel):
|
||||
@@ -12,17 +19,26 @@ class ChoreBase(BaseModel):
|
||||
description: Optional[str] = None
|
||||
room: str
|
||||
frequency: ChoreFrequency
|
||||
<<<<<<< HEAD
|
||||
points: Optional[int] = 0
|
||||
image_url: Optional[str] = None
|
||||
assignment_type: Optional[ChoreAssignmentType] = ChoreAssignmentType.ANY_ONE
|
||||
=======
|
||||
assigned_user_id: Optional[int] = None
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
due_date: Optional[Union[datetime, date, str]] = None
|
||||
|
||||
@field_validator('due_date', mode='before')
|
||||
@classmethod
|
||||
def parse_due_date(cls, v):
|
||||
"""Parse due_date to handle various formats."""
|
||||
<<<<<<< HEAD
|
||||
if v is None or v == '' or isinstance(v, (datetime, date)):
|
||||
return None if v == '' else v
|
||||
=======
|
||||
if v is None or isinstance(v, (datetime, date)):
|
||||
return v
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
if isinstance(v, str):
|
||||
# Try parsing as datetime first
|
||||
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d']:
|
||||
@@ -30,14 +46,22 @@ class ChoreBase(BaseModel):
|
||||
return datetime.strptime(v, fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
<<<<<<< HEAD
|
||||
# If no format matches, return None instead of the invalid string
|
||||
return None
|
||||
return None
|
||||
=======
|
||||
return v
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class ChoreCreate(ChoreBase):
|
||||
"""Schema for creating a chore."""
|
||||
<<<<<<< HEAD
|
||||
assigned_user_ids: Optional[List[int]] = [] # Multiple users can be assigned
|
||||
=======
|
||||
pass
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class ChoreUpdate(BaseModel):
|
||||
@@ -46,18 +70,28 @@ class ChoreUpdate(BaseModel):
|
||||
description: Optional[str] = None
|
||||
room: Optional[str] = None
|
||||
frequency: Optional[ChoreFrequency] = None
|
||||
<<<<<<< HEAD
|
||||
points: Optional[int] = None
|
||||
status: Optional[ChoreStatus] = None
|
||||
assignment_type: Optional[ChoreAssignmentType] = None
|
||||
assigned_user_ids: Optional[List[int]] = None # Multiple users
|
||||
=======
|
||||
status: Optional[ChoreStatus] = None
|
||||
assigned_user_id: Optional[int] = None
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
due_date: Optional[Union[datetime, date, str]] = None
|
||||
|
||||
@field_validator('due_date', mode='before')
|
||||
@classmethod
|
||||
def parse_due_date(cls, v):
|
||||
"""Parse due_date to handle various formats."""
|
||||
<<<<<<< HEAD
|
||||
if v is None or v == '' or isinstance(v, (datetime, date)):
|
||||
return None if v == '' else v
|
||||
=======
|
||||
if v is None or isinstance(v, (datetime, date)):
|
||||
return v
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
if isinstance(v, str):
|
||||
# Try parsing as datetime first
|
||||
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d']:
|
||||
@@ -65,6 +99,7 @@ class ChoreUpdate(BaseModel):
|
||||
return datetime.strptime(v, fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
<<<<<<< HEAD
|
||||
# If no format matches, return None instead of the invalid string
|
||||
return None
|
||||
return None
|
||||
@@ -72,14 +107,24 @@ class ChoreUpdate(BaseModel):
|
||||
|
||||
class AssignedUserDetail(BaseModel):
|
||||
"""User info for chore assignment."""
|
||||
=======
|
||||
return v
|
||||
|
||||
|
||||
class AssignedUser(BaseModel):
|
||||
"""Minimal user info for chore assignment."""
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
username: str
|
||||
full_name: str
|
||||
<<<<<<< HEAD
|
||||
avatar_url: Optional[str] = None
|
||||
birthday: Optional[date] = None
|
||||
completed_at: Optional[datetime] = None # When this user completed the chore
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class Chore(ChoreBase):
|
||||
@@ -88,6 +133,7 @@ class Chore(ChoreBase):
|
||||
|
||||
id: int
|
||||
status: ChoreStatus
|
||||
<<<<<<< HEAD
|
||||
points: int
|
||||
assignment_type: ChoreAssignmentType
|
||||
assigned_users: List[AssignedUserDetail] = [] # Multiple users with completion status
|
||||
@@ -97,3 +143,9 @@ class Chore(ChoreBase):
|
||||
|
||||
# Legacy field for backward compatibility
|
||||
assigned_user_id: Optional[int] = None
|
||||
=======
|
||||
assigned_user: Optional[AssignedUser] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
fastapi==0.115.0
|
||||
uvicorn[standard]==0.32.0
|
||||
sqlalchemy==2.0.36
|
||||
@@ -9,3 +10,6 @@ pydantic==2.10.3
|
||||
pydantic-settings==2.6.1
|
||||
python-dotenv==1.0.1
|
||||
email-validator==2.2.0
|
||||
=======
|
||||
ZmFzdGFwaT09MC4xMTUuMAp1dmljb3JuW3N0YW5kYXJkXT09MC4zMi4wCnNxbGFsY2hlbXk9PTIuMC4zNgpweXRob24tam9zZVtjcnlwdG9ncmFwaHldPT0zLjMuMApiY3J5cHQ9PTQuMi4wCnBhc3NsaWJbYmNyeXB0XT09MS43LjQKcHl0aG9uLW11bHRpcGFydD09MC4wLjEyCnB5ZGFudGljPT0yLjEwLjMKcHlkYW50aWMtc2V0dGluZ3M9PTIuNi4xCnB5dGhvbi1kb3RlbnY9PTEuMC4xCmVtYWlsLXZhbGlkYXRvcj09Mi4yLjAK
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
Reference in New Issue
Block a user