import json from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session, joinedload from typing import List from database import get_db from models.message import StaffMessage, StaffMessageAck, QuickMessageTemplate from models.user import User from schemas.message import ( SendMessageRequest, StaffMessageOut, QuickTemplateCreate, QuickTemplateUpdate, QuickTemplateOut, ) from routers.deps import get_current_user, require_manager router = APIRouter() def _load_msg(db: Session, msg_id: int) -> StaffMessage: """Reload a message with sender and acks eagerly loaded.""" return db.query(StaffMessage).options( joinedload(StaffMessage.sender), joinedload(StaffMessage.acks), ).filter(StaffMessage.id == msg_id).one() def _message_out(msg: StaffMessage) -> StaffMessageOut: sender_name = None try: sender_name = msg.sender.username if msg.sender else None except Exception: pass return StaffMessageOut( id=msg.id, sender_id=msg.sender_id, sender_name=sender_name, body=msg.body, target_waiter_ids=msg.target_waiter_ids, table_ids=msg.table_ids, created_at=msg.created_at, acked_by=[ack.waiter_id for ack in msg.acks], ) # ─── Quick templates ────────────────────────────────────────────────────────── @router.get("/templates", response_model=List[QuickTemplateOut]) def list_templates( db: Session = Depends(get_db), user: User = Depends(get_current_user), ): return db.query(QuickMessageTemplate).filter( QuickMessageTemplate.is_active == True ).order_by(QuickMessageTemplate.sort_order, QuickMessageTemplate.id).all() @router.post("/templates", response_model=QuickTemplateOut, status_code=status.HTTP_201_CREATED) def create_template( body: QuickTemplateCreate, db: Session = Depends(get_db), user: User = Depends(require_manager), ): t = QuickMessageTemplate(**body.model_dump()) db.add(t) db.commit() db.refresh(t) return t @router.put("/templates/{template_id}", response_model=QuickTemplateOut) def update_template( template_id: int, body: QuickTemplateUpdate, db: Session = Depends(get_db), user: User = Depends(require_manager), ): t = db.query(QuickMessageTemplate).filter(QuickMessageTemplate.id == template_id).first() if not t: raise HTTPException(status_code=404, detail="Template not found") for k, v in body.model_dump(exclude_unset=True).items(): setattr(t, k, v) db.commit() db.refresh(t) return t @router.delete("/templates/{template_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_template( template_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager), ): t = db.query(QuickMessageTemplate).filter(QuickMessageTemplate.id == template_id).first() if not t: raise HTTPException(status_code=404, detail="Template not found") t.is_active = False db.commit() # ─── Staff messages ─────────────────────────────────────────────────────────── @router.post("/send", response_model=StaffMessageOut, status_code=status.HTTP_201_CREATED) def send_message( body: SendMessageRequest, db: Session = Depends(get_db), user: User = Depends(require_manager), ): msg = StaffMessage( sender_id=user.id, body=body.body, target_waiter_ids=json.dumps(body.target_waiter_ids), table_ids=json.dumps(body.table_ids or []), ) db.add(msg) db.commit() msg = _load_msg(db, msg.id) return _message_out(msg) @router.get("/unread", response_model=List[StaffMessageOut]) def get_unread_messages( db: Session = Depends(get_db), user: User = Depends(get_current_user), ): """ Returns messages targeting this waiter that they haven't acked yet. A message targets a waiter if their ID is in target_waiter_ids, OR if target_waiter_ids is empty (broadcast to all). """ all_msgs = db.query(StaffMessage).options( joinedload(StaffMessage.sender), joinedload(StaffMessage.acks), ).order_by(StaffMessage.created_at.desc()).limit(200).all() acked_ids = { ack.message_id for ack in db.query(StaffMessageAck).filter(StaffMessageAck.waiter_id == user.id).all() } result = [] for msg in all_msgs: if msg.id in acked_ids: continue targets = json.loads(msg.target_waiter_ids or "[]") # Empty list = broadcast to all if not targets or user.id in targets: result.append(_message_out(msg)) return result @router.get("/recent", response_model=List[StaffMessageOut]) def get_recent_messages( limit: int = 10, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): """Last N messages targeting this user (for notification history drawer).""" all_msgs = db.query(StaffMessage).options( joinedload(StaffMessage.sender), joinedload(StaffMessage.acks), ).order_by(StaffMessage.created_at.desc()).limit(200).all() result = [] for msg in all_msgs: targets = json.loads(msg.target_waiter_ids or "[]") if not targets or user.id in targets: result.append(_message_out(msg)) if len(result) >= limit: break return result @router.post("/{message_id}/ack", status_code=status.HTTP_204_NO_CONTENT) def ack_message( message_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): msg = db.query(StaffMessage).filter(StaffMessage.id == message_id).first() if not msg: raise HTTPException(status_code=404, detail="Message not found") existing = db.query(StaffMessageAck).filter( StaffMessageAck.message_id == message_id, StaffMessageAck.waiter_id == user.id, ).first() if not existing: db.add(StaffMessageAck(message_id=message_id, waiter_id=user.id)) db.commit() @router.get("/all", response_model=List[StaffMessageOut]) def list_all_messages( limit: int = 50, db: Session = Depends(get_db), user: User = Depends(require_manager), ): msgs = db.query(StaffMessage).options( joinedload(StaffMessage.sender), joinedload(StaffMessage.acks), ).order_by(StaffMessage.created_at.desc()).limit(limit).all() return [_message_out(m) for m in msgs]