from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import require_role from app.db.database import get_db from app.db.models import User from app.schemas.timeline import TimelineCreate, TimelineUpdate, TimelineOut from app.schemas.common import PageResponse from app.services.timeline_service import ( create_timeline, update_timeline, delete_timeline, get_timeline_by_id, list_timelines, add_images_to_timeline, ) from app.services.cos_service import upload_image router = APIRouter(prefix="/api/timeline", tags=["timeline"]) @router.get("/", response_model=PageResponse[TimelineOut]) async def get_timelines( page: int = 1, page_size: int = 20, class_id: int | None = None, user: User = Depends(require_role("super_admin", "class_admin", "student")), db: AsyncSession = Depends(get_db), ): effective_class_id = class_id if user.role == "super_admin" and class_id else user.class_id if effective_class_id is None: return PageResponse(items=[], total=0, page=page, page_size=page_size, total_pages=0) posts, total = await list_timelines(db, effective_class_id, page, page_size) total_pages = (total + page_size - 1) // page_size items = [] for p in posts: items.append( TimelineOut( id=p.id, class_id=p.class_id, author_id=p.author_id, author_name=p.author.name if p.author else "Unknown", title=p.title, content=p.content, image_urls=p.get_image_urls_list(), created_at=p.created_at, updated_at=p.updated_at, ) ) return PageResponse( items=items, total=total, page=page, page_size=page_size, total_pages=total_pages ) @router.post("/", response_model=TimelineOut) async def create_new_timeline( data: TimelineCreate, class_id: int | None = None, user: User = Depends(require_role("super_admin", "class_admin")), db: AsyncSession = Depends(get_db), ): effective_class_id = class_id if user.role == "super_admin" and class_id else user.class_id if effective_class_id is None: raise HTTPException(status_code=400, detail="You are not assigned to a class") post = await create_timeline(db, effective_class_id, user.id, data) return TimelineOut( id=post.id, class_id=post.class_id, author_id=post.author_id, author_name=user.name, title=post.title, content=post.content, image_urls=[], created_at=post.created_at, updated_at=post.updated_at, ) @router.post("/{post_id}/images") async def upload_timeline_images( post_id: int, files: list[UploadFile] = File(...), user: User = Depends(require_role("super_admin", "class_admin")), db: AsyncSession = Depends(get_db), ): post = await get_timeline_by_id(db, post_id) if post is None: raise HTTPException(status_code=404, detail="Timeline post not found") # Verify post belongs to user's class (super_admin can access any) if user.role != "super_admin" and post.class_id != user.class_id: raise HTTPException(status_code=403, detail="Access denied") urls = [] for f in files: contents = await f.read() if len(contents) > 10 * 1024 * 1024: # 10MB limit raise HTTPException( status_code=400, detail=f"File {f.filename} too large (max 10MB)" ) if f.content_type not in {"image/jpeg", "image/png", "image/gif", "image/webp"}: raise HTTPException( status_code=400, detail=f"File {f.filename} has invalid type" ) url = upload_image( f"timeline/{post_id}", f.filename or "image.jpg", contents, f.content_type ) urls.append(url) await add_images_to_timeline(db, post, urls) return {"image_urls": urls} @router.put("/{post_id}", response_model=TimelineOut) async def update_existing_timeline( post_id: int, data: TimelineUpdate, user: User = Depends(require_role("super_admin", "class_admin")), db: AsyncSession = Depends(get_db), ): post = await get_timeline_by_id(db, post_id) if post is None: raise HTTPException(status_code=404, detail="Timeline post not found") if user.role != "super_admin" and post.class_id != user.class_id: raise HTTPException(status_code=403, detail="Access denied") updated = await update_timeline(db, post, data) return TimelineOut( id=updated.id, class_id=updated.class_id, author_id=updated.author_id, author_name=user.name, title=updated.title, content=updated.content, image_urls=updated.get_image_urls_list(), created_at=updated.created_at, updated_at=updated.updated_at, ) @router.delete("/{post_id}") async def delete_existing_timeline( post_id: int, user: User = Depends(require_role("super_admin", "class_admin")), db: AsyncSession = Depends(get_db), ): post = await get_timeline_by_id(db, post_id) if post is None: raise HTTPException(status_code=404, detail="Timeline post not found") if user.role != "super_admin" and post.class_id != user.class_id: raise HTTPException(status_code=403, detail="Access denied") await delete_timeline(db, post) return {"message": "Timeline post deleted"}