Add strike events to activity log
Record a StrikeEvent row whenever a strike is added or removed, and surface them in the activity log timeline with timestamp, child name, and whether the strike was added or removed. - New strike_events table (auto-created on startup) - children router records prev/new strikes on every update - GET /api/logs/strikes and DELETE /api/logs/strikes/:id endpoints - Log view merges strike entries into the timeline (red dot, "✕ Strike added (2/3)" / "↩ Strike removed (1/3)") Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ from sqlalchemy import select
|
||||
|
||||
from app.dependencies import get_db, get_current_user
|
||||
from app.models.child import Child
|
||||
from app.models.strike import StrikeEvent
|
||||
from app.models.user import User
|
||||
from app.schemas.child import ChildCreate, ChildOut, ChildUpdate
|
||||
from app.websocket.manager import manager
|
||||
@@ -89,7 +90,9 @@ async def update_strikes(
|
||||
child = result.scalar_one_or_none()
|
||||
if not child:
|
||||
raise HTTPException(status_code=404, detail="Child not found")
|
||||
prev = child.strikes
|
||||
child.strikes = body.strikes
|
||||
db.add(StrikeEvent(child_id=child.id, prev_strikes=prev, new_strikes=body.strikes))
|
||||
await db.commit()
|
||||
await db.refresh(child)
|
||||
await manager.broadcast(child_id, {"event": "strikes_update", "strikes": child.strikes})
|
||||
|
||||
@@ -2,7 +2,7 @@ from datetime import date
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.dependencies import get_db, get_current_user
|
||||
@@ -10,9 +10,10 @@ from app.models.activity import ActivityLog
|
||||
from app.models.child import Child
|
||||
from app.models.session import DailySession, TimerEvent
|
||||
from app.models.schedule import ScheduleBlock
|
||||
from app.models.strike import StrikeEvent
|
||||
from app.models.subject import Subject # noqa: F401
|
||||
from app.models.user import User
|
||||
from app.schemas.activity import ActivityLogCreate, ActivityLogOut, ActivityLogUpdate, TimelineEventOut, TimelineEventUpdate
|
||||
from app.schemas.activity import ActivityLogCreate, ActivityLogOut, ActivityLogUpdate, StrikeEventOut, TimelineEventOut, TimelineEventUpdate
|
||||
|
||||
router = APIRouter(prefix="/api/logs", tags=["logs"])
|
||||
|
||||
@@ -119,6 +120,59 @@ async def delete_timeline_event(
|
||||
await db.commit()
|
||||
|
||||
|
||||
@router.get("/strikes", response_model=list[StrikeEventOut])
|
||||
async def get_strike_events(
|
||||
child_id: int | None = None,
|
||||
log_date: date | None = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = (
|
||||
select(StrikeEvent)
|
||||
.join(Child, StrikeEvent.child_id == Child.id)
|
||||
.where(Child.user_id == current_user.id)
|
||||
.options(selectinload(StrikeEvent.child))
|
||||
.order_by(StrikeEvent.occurred_at.desc())
|
||||
)
|
||||
if child_id:
|
||||
query = query.where(StrikeEvent.child_id == child_id)
|
||||
if log_date:
|
||||
query = query.where(func.date(StrikeEvent.occurred_at) == log_date)
|
||||
|
||||
result = await db.execute(query)
|
||||
events = result.scalars().all()
|
||||
return [
|
||||
StrikeEventOut(
|
||||
id=e.id,
|
||||
child_id=e.child_id,
|
||||
child_name=e.child.name,
|
||||
occurred_at=e.occurred_at,
|
||||
log_date=e.occurred_at.date(),
|
||||
prev_strikes=e.prev_strikes,
|
||||
new_strikes=e.new_strikes,
|
||||
)
|
||||
for e in events
|
||||
]
|
||||
|
||||
|
||||
@router.delete("/strikes/{strike_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_strike_event(
|
||||
strike_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(StrikeEvent)
|
||||
.join(Child, StrikeEvent.child_id == Child.id)
|
||||
.where(StrikeEvent.id == strike_id, Child.user_id == current_user.id)
|
||||
)
|
||||
event = result.scalar_one_or_none()
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Strike event not found")
|
||||
await db.delete(event)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@router.get("", response_model=list[ActivityLogOut])
|
||||
async def list_logs(
|
||||
child_id: int | None = None,
|
||||
|
||||
Reference in New Issue
Block a user