298 lines
8.9 KiB
Python
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
|
|
}
|