Compare commits

..

2 Commits

Author SHA1 Message Date
9c5aafc57b Merge pull request 'Add missing endpoints, fix batch updates, and implement RSVP status' (#21) from dev into loaf-prod
Reviewed-on: #21
2026-01-05 18:08:21 +00:00
Koncept Kit
314380eec6 Add missing endpoints, fix batch updates, and implement RSVP status
## New Endpoints
- **GET /admin/events/{event_id}**: Get single event details (admin)
  - Allows viewing unpublished events
  - Returns full event with RSVP count

## Enhanced Endpoints
- **PUT /admin/events/{event_id}/attendance**: Accept batch updates
  - Add BatchAttendanceUpdate model for array of updates
  - Support both single and bulk attendance marking
  - Return count of updated records

- **GET /events**: Include user RSVP status in response
  - Query current user's RSVP for each event
  - Enable calendar color coding by status

- **GET /events/{event_id}**: Include user RSVP status
  - Query current user's RSVP for event details
  - Maintain consistency with list endpoint

## Bug Fixes
- **GET /members/event-activity**: Fix timezone comparison
  - Add timezone-aware conversion for event.end_at
  - Resolve "can't compare offset-naive and offset-aware" error

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-06 01:03:01 +07:00

121
server.py
View File

@@ -364,6 +364,9 @@ class AttendanceUpdate(BaseModel):
user_id: str
attended: bool
class BatchAttendanceUpdate(BaseModel):
updates: list[AttendanceUpdate]
class UpdateUserStatusRequest(BaseModel):
status: str
@@ -1499,7 +1502,14 @@ async def get_events(
EventRSVP.rsvp_status == RSVPStatus.yes
).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(
id=str(event.id),
title=event.title,
@@ -1512,7 +1522,7 @@ async def get_events(
created_by=str(event.created_by),
created_at=event.created_at,
rsvp_count=rsvp_count,
user_rsvp_status=None
user_rsvp_status=user_rsvp_status
))
return result
@@ -1532,8 +1542,13 @@ async def get_event(
EventRSVP.rsvp_status == RSVPStatus.yes
).count()
# No user_rsvp_status in public endpoint
user_rsvp = None
# 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
return EventResponse(
id=str(event.id),
@@ -1547,7 +1562,7 @@ async def get_event(
created_by=str(event.created_by),
created_at=event.created_at,
rsvp_count=rsvp_count,
user_rsvp_status=user_rsvp
user_rsvp_status=user_rsvp_status
)
@api_router.post("/events/{event_id}/rsvp")
@@ -1618,7 +1633,9 @@ async def get_my_event_activity(
}
# 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)
else:
past_events.append(event_data)
@@ -3796,6 +3813,37 @@ async def update_event(
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")
async def get_event_rsvps(
event_id: str,
@@ -3826,46 +3874,53 @@ async def get_event_rsvps(
@api_router.put("/admin/events/{event_id}/attendance")
async def mark_attendance(
event_id: str,
request: AttendanceUpdate,
request: BatchAttendanceUpdate,
current_user: User = Depends(require_permission("events.attendance")),
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()
if not event:
raise HTTPException(status_code=404, detail="Event not found")
rsvp = db.query(EventRSVP).filter(
EventRSVP.event_id == event_id,
EventRSVP.user_id == request.user_id
).first()
updated_count = 0
# Auto-create RSVP if it doesn't exist (for retroactive attendance marking)
if not rsvp:
rsvp = EventRSVP(
event_id=event_id,
user_id=request.user_id,
rsvp_status=RSVPStatus.yes, # Default to 'yes' for attended events
attended=False,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
db.add(rsvp)
db.flush() # Get the ID without committing
# Process each update in the batch
for update in request.updates:
rsvp = db.query(EventRSVP).filter(
EventRSVP.event_id == event_id,
EventRSVP.user_id == update.user_id
).first()
rsvp.attended = request.attended
rsvp.attended_at = datetime.now(timezone.utc) if request.attended else None
rsvp.updated_at = datetime.now(timezone.utc)
# Auto-create RSVP if it doesn't exist (for retroactive attendance marking)
if not rsvp:
rsvp = EventRSVP(
event_id=event_id,
user_id=update.user_id,
rsvp_status=RSVPStatus.yes, # Default to 'yes' for attended events
attended=False,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
db.add(rsvp)
db.flush() # Get the ID without committing
# If user attended and they were pending validation, update their status
if request.attended:
user = db.query(User).filter(User.id == request.user_id).first()
if user and user.status == UserStatus.pending_validation:
user.status = UserStatus.pre_validated
user.updated_at = datetime.now(timezone.utc)
rsvp.attended = update.attended
rsvp.attended_at = datetime.now(timezone.utc) if update.attended else None
rsvp.updated_at = datetime.now(timezone.utc)
# If user attended and they were pending validation, update their status
if update.attended:
user = db.query(User).filter(User.id == update.user_id).first()
if user and user.status == UserStatus.pending_validation:
user.status = UserStatus.pre_validated
user.updated_at = datetime.now(timezone.utc)
updated_count += 1
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")
async def get_admin_events(