Files
family-hub/backend/app/api/v1/public.py

298 lines
8.9 KiB
Python

"""Public API endpoints for kiosk view (no authentication required)."""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import date
from app.core.database import get_db
from app.models.user import User
from app.models.chore import Chore
from app.models.chore_assignment import ChoreAssignment
from app.models.chore_completion_log import ChoreCompletionLog
router = APIRouter()
@router.get("/users")
async def get_public_users(db: Session = Depends(get_db)):
"""Get all active users (public endpoint for kiosk)."""
users = db.query(User).filter(User.is_active == True).all()
return [
{
"id": user.id,
"username": user.username,
"full_name": user.full_name,
"avatar_url": user.avatar_url,
"birthday": user.birthday,
"is_active": user.is_active,
}
for user in users
]
@router.get("/chores")
async def get_public_chores(
user_id: Optional[int] = None,
exclude_birthdays: bool = False,
db: Session = Depends(get_db)
):
"""
Get chores with optional filtering (public endpoint for kiosk).
- user_id: Filter by assigned user
- exclude_birthdays: Hide chores for users whose birthday is today
"""
query = db.query(Chore)
# Filter by user if specified
if user_id:
query = query.join(ChoreAssignment).filter(
ChoreAssignment.user_id == user_id
)
chores = query.all()
# Filter out birthday chores if requested
if exclude_birthdays:
today = date.today()
filtered_chores = []
for chore in chores:
# Get all assignments for this chore
assignments = db.query(ChoreAssignment).filter(
ChoreAssignment.chore_id == chore.id
).all()
# Check if any assigned user has birthday today
has_birthday = False
for assignment in assignments:
user = db.query(User).filter(User.id == assignment.user_id).first()
if user and user.birthday:
if user.birthday.month == today.month and user.birthday.day == today.day:
has_birthday = True
break
if not has_birthday:
filtered_chores.append(chore)
chores = filtered_chores
# Build response with assigned users info
result = []
for chore in chores:
# Get assignments for this chore
assignments = db.query(ChoreAssignment).filter(
ChoreAssignment.chore_id == chore.id
).all()
assigned_users = []
for assignment in assignments:
user = db.query(User).filter(User.id == assignment.user_id).first()
if user:
assigned_users.append({
"id": user.id,
"username": user.username,
"full_name": user.full_name,
"avatar_url": user.avatar_url,
"birthday": user.birthday,
"completed_at": assignment.completed_at
})
chore_dict = {
"id": chore.id,
"title": chore.title,
"description": chore.description,
"room": chore.room,
"frequency": chore.frequency,
"points": chore.points,
"image_url": chore.image_url,
"assignment_type": chore.assignment_type,
"status": chore.status,
"due_date": chore.due_date,
"created_at": chore.created_at,
"updated_at": chore.updated_at,
"completed_at": chore.completed_at,
"assigned_users": assigned_users,
"assigned_user_id": None # Legacy field
}
result.append(chore_dict)
return result
@router.post("/chores/{chore_id}/complete")
async def complete_public_chore(
chore_id: int,
user_id: int,
helper_ids: Optional[List[int]] = None,
db: Session = Depends(get_db)
):
"""
Mark a chore as complete for a specific user (public endpoint for kiosk).
Query params:
- user_id: The user completing the chore
- helper_ids: Optional list of other user IDs who helped
"""
# Get chore
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"
)
# Get 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"
)
# Get assignment
assignment = db.query(ChoreAssignment).filter(
ChoreAssignment.chore_id == chore_id,
ChoreAssignment.user_id == user_id
).first()
if not assignment:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not assigned to this chore"
)
# Mark as complete
from datetime import datetime
completion_time = datetime.now()
assignment.completed_at = completion_time
# CREATE COMPLETION LOG ENTRY
completion_log = ChoreCompletionLog(
chore_id=chore_id,
user_id=user_id,
completed_at=completion_time,
notes=None # Kiosk doesn't support notes yet
)
db.add(completion_log)
# If helpers provided, mark them as completed too
if helper_ids:
for helper_id in helper_ids:
helper_assignment = db.query(ChoreAssignment).filter(
ChoreAssignment.chore_id == chore_id,
ChoreAssignment.user_id == helper_id
).first()
# Create assignment if doesn't exist (helper claimed they helped)
if not helper_assignment:
helper_assignment = ChoreAssignment(
chore_id=chore_id,
user_id=helper_id,
completed_at=datetime.now()
)
db.add(helper_assignment)
else:
helper_assignment.completed_at = datetime.now()
# CREATE COMPLETION LOG FOR HELPER
helper_log = ChoreCompletionLog(
chore_id=chore_id,
user_id=helper_id,
completed_at=datetime.now(),
notes=f"Helped {user.full_name or user.username}"
)
db.add(helper_log)
# Check if chore is complete based on assignment_type
all_assignments = db.query(ChoreAssignment).filter(
ChoreAssignment.chore_id == chore_id
).all()
if chore.assignment_type == "any_one":
# Only one person needs to complete
if assignment.completed_at:
chore.status = 'completed'
chore.completed_at = datetime.now()
else: # all_assigned
# All assigned must complete
all_complete = all([a.completed_at is not None for a in all_assignments])
if all_complete:
chore.status = 'completed'
chore.completed_at = datetime.now()
else:
chore.status = 'in_progress'
db.commit()
return {
"message": "Chore marked as complete",
"chore_id": chore_id,
"user_id": user_id,
"helper_ids": helper_ids or [],
"chore_status": chore.status
}
@router.post("/chores/{chore_id}/claim")
async def claim_public_chore(
chore_id: int,
user_id: int,
db: Session = Depends(get_db)
):
"""
Claim an available chore (add user as assigned).
Query params:
- user_id: The user claiming the chore
"""
# Get chore
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"
)
# Get 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"
)
# Check if already assigned
existing = db.query(ChoreAssignment).filter(
ChoreAssignment.chore_id == chore_id,
ChoreAssignment.user_id == user_id
).first()
if existing:
return {
"message": "Already assigned to this chore",
"chore_id": chore_id,
"user_id": user_id
}
# Create assignment
assignment = ChoreAssignment(
chore_id=chore_id,
user_id=user_id
)
db.add(assignment)
# Update chore status if needed
if chore.status == 'pending':
chore.status = 'in_progress'
db.commit()
return {
"message": "Chore claimed successfully",
"chore_id": chore_id,
"user_id": user_id
}