Merge pull request 'Add missing endpoints, fix batch updates, and implement RSVP status' (#21) from dev into loaf-prod

Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
2026-01-05 18:08:21 +00:00

View File

@@ -364,6 +364,9 @@ class AttendanceUpdate(BaseModel):
user_id: str user_id: str
attended: bool attended: bool
class BatchAttendanceUpdate(BaseModel):
updates: list[AttendanceUpdate]
class UpdateUserStatusRequest(BaseModel): class UpdateUserStatusRequest(BaseModel):
status: str status: str
@@ -1499,7 +1502,14 @@ async def get_events(
EventRSVP.rsvp_status == RSVPStatus.yes EventRSVP.rsvp_status == RSVPStatus.yes
).count() ).count()
# No user_rsvp_status in public endpoint # Get current user's RSVP status for this event
user_rsvp = db.query(EventRSVP).filter(
EventRSVP.event_id == event.id,
EventRSVP.user_id == current_user.id
).first()
user_rsvp_status = user_rsvp.rsvp_status.value if user_rsvp else None
result.append(EventResponse( result.append(EventResponse(
id=str(event.id), id=str(event.id),
title=event.title, title=event.title,
@@ -1512,7 +1522,7 @@ async def get_events(
created_by=str(event.created_by), created_by=str(event.created_by),
created_at=event.created_at, created_at=event.created_at,
rsvp_count=rsvp_count, rsvp_count=rsvp_count,
user_rsvp_status=None user_rsvp_status=user_rsvp_status
)) ))
return result return result
@@ -1532,8 +1542,13 @@ async def get_event(
EventRSVP.rsvp_status == RSVPStatus.yes EventRSVP.rsvp_status == RSVPStatus.yes
).count() ).count()
# No user_rsvp_status in public endpoint # Get current user's RSVP status for this event
user_rsvp = None user_rsvp = db.query(EventRSVP).filter(
EventRSVP.event_id == event_id,
EventRSVP.user_id == current_user.id
).first()
user_rsvp_status = user_rsvp.rsvp_status.value if user_rsvp else None
return EventResponse( return EventResponse(
id=str(event.id), id=str(event.id),
@@ -1547,7 +1562,7 @@ async def get_event(
created_by=str(event.created_by), created_by=str(event.created_by),
created_at=event.created_at, created_at=event.created_at,
rsvp_count=rsvp_count, rsvp_count=rsvp_count,
user_rsvp_status=user_rsvp user_rsvp_status=user_rsvp_status
) )
@api_router.post("/events/{event_id}/rsvp") @api_router.post("/events/{event_id}/rsvp")
@@ -1618,7 +1633,9 @@ async def get_my_event_activity(
} }
# Separate upcoming vs past events # Separate upcoming vs past events
if event.end_at > now: # Ensure timezone-aware comparison
event_end_at = event.end_at.replace(tzinfo=timezone.utc) if event.end_at.tzinfo is None else event.end_at
if event_end_at > now:
upcoming_events.append(event_data) upcoming_events.append(event_data)
else: else:
past_events.append(event_data) past_events.append(event_data)
@@ -3796,6 +3813,37 @@ async def update_event(
return {"message": "Event updated successfully"} return {"message": "Event updated successfully"}
@api_router.get("/admin/events/{event_id}", response_model=EventResponse)
async def get_admin_event(
event_id: str,
current_user: User = Depends(require_permission("events.view")),
db: Session = Depends(get_db)
):
"""Get single event details (admin) - allows viewing unpublished events"""
event = db.query(Event).filter(Event.id == event_id).first()
if not event:
raise HTTPException(status_code=404, detail="Event not found")
rsvp_count = db.query(EventRSVP).filter(
EventRSVP.event_id == event.id,
EventRSVP.rsvp_status == RSVPStatus.yes
).count()
return EventResponse(
id=str(event.id),
title=event.title,
description=event.description,
start_at=event.start_at,
end_at=event.end_at,
location=event.location,
capacity=event.capacity,
published=event.published,
created_by=str(event.created_by),
created_at=event.created_at,
rsvp_count=rsvp_count,
user_rsvp_status=None
)
@api_router.get("/admin/events/{event_id}/rsvps") @api_router.get("/admin/events/{event_id}/rsvps")
async def get_event_rsvps( async def get_event_rsvps(
event_id: str, event_id: str,
@@ -3826,24 +3874,29 @@ async def get_event_rsvps(
@api_router.put("/admin/events/{event_id}/attendance") @api_router.put("/admin/events/{event_id}/attendance")
async def mark_attendance( async def mark_attendance(
event_id: str, event_id: str,
request: AttendanceUpdate, request: BatchAttendanceUpdate,
current_user: User = Depends(require_permission("events.attendance")), current_user: User = Depends(require_permission("events.attendance")),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""Mark attendance for one or more users (supports batch updates)"""
event = db.query(Event).filter(Event.id == event_id).first() event = db.query(Event).filter(Event.id == event_id).first()
if not event: if not event:
raise HTTPException(status_code=404, detail="Event not found") raise HTTPException(status_code=404, detail="Event not found")
updated_count = 0
# Process each update in the batch
for update in request.updates:
rsvp = db.query(EventRSVP).filter( rsvp = db.query(EventRSVP).filter(
EventRSVP.event_id == event_id, EventRSVP.event_id == event_id,
EventRSVP.user_id == request.user_id EventRSVP.user_id == update.user_id
).first() ).first()
# Auto-create RSVP if it doesn't exist (for retroactive attendance marking) # Auto-create RSVP if it doesn't exist (for retroactive attendance marking)
if not rsvp: if not rsvp:
rsvp = EventRSVP( rsvp = EventRSVP(
event_id=event_id, event_id=event_id,
user_id=request.user_id, user_id=update.user_id,
rsvp_status=RSVPStatus.yes, # Default to 'yes' for attended events rsvp_status=RSVPStatus.yes, # Default to 'yes' for attended events
attended=False, attended=False,
created_at=datetime.now(timezone.utc), created_at=datetime.now(timezone.utc),
@@ -3852,20 +3905,22 @@ async def mark_attendance(
db.add(rsvp) db.add(rsvp)
db.flush() # Get the ID without committing db.flush() # Get the ID without committing
rsvp.attended = request.attended rsvp.attended = update.attended
rsvp.attended_at = datetime.now(timezone.utc) if request.attended else None rsvp.attended_at = datetime.now(timezone.utc) if update.attended else None
rsvp.updated_at = datetime.now(timezone.utc) rsvp.updated_at = datetime.now(timezone.utc)
# If user attended and they were pending validation, update their status # If user attended and they were pending validation, update their status
if request.attended: if update.attended:
user = db.query(User).filter(User.id == request.user_id).first() user = db.query(User).filter(User.id == update.user_id).first()
if user and user.status == UserStatus.pending_validation: if user and user.status == UserStatus.pending_validation:
user.status = UserStatus.pre_validated user.status = UserStatus.pre_validated
user.updated_at = datetime.now(timezone.utc) user.updated_at = datetime.now(timezone.utc)
updated_count += 1
db.commit() db.commit()
return {"message": "Attendance marked successfully"} return {"message": f"Attendance marked successfully for {updated_count} {'person' if updated_count == 1 else 'people'}"}
@api_router.get("/admin/events") @api_router.get("/admin/events")
async def get_admin_events( async def get_admin_events(