Phase 3.1: Enhanced Chore Logging and Reporting System
This commit is contained in:
297
backend/app/api/v1/public.py
Normal file
297
backend/app/api/v1/public.py
Normal file
@@ -0,0 +1,297 @@
|
||||
"""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
|
||||
}
|
||||
Reference in New Issue
Block a user