Coverage for src/data_api/api/routers/calendars.py: 100%
42 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
1"""Campus calendar endpoints."""
3from datetime import date
4from typing import Optional
6from fastapi import APIRouter, HTTPException, Path, Query, Response
8from data_api.api.schemas.calendars import Calendar, CalendarEvent
9from data_api.api.schemas.errors import ErrorResponse
10from data_api.api.schemas.responses import COUNT_HEADERS, SNAPSHOT_RESPONSES
11from data_api.runtime import get_runtime
13router = APIRouter(responses=SNAPSHOT_RESPONSES)
15CALENDAR_ID_PATH = Path(
16 ..., description="行事曆 id,可由 /calendars 取得,例如 academic、library-main"
17)
20@router.get("", response_model=list[Calendar], operation_id="getAllCalendars")
21async def get_all_calendars(response: Response):
22 """
23 取得所有校園行事曆的資訊(不含事件)。
24 包含校級與圖書館開館時間行事曆。
25 資料來源:[清華校園資料](https://data.nthusa.tw/calendars.json)、
26 [圖書館行事曆](https://data.nthusa.tw/libraries/calendars.json)
27 """
28 commit_hash, calendars = await get_runtime().calendars.get_all_calendars()
29 if commit_hash is not None:
30 response.headers["X-Data-Commit-Hash"] = commit_hash
31 return calendars
34@router.get(
35 "/{calendar_id}",
36 response_model=Calendar,
37 operation_id="getCalendar",
38 responses={404: {"model": ErrorResponse, "description": "Calendar not found"}},
39)
40async def get_calendar(response: Response, calendar_id: str = CALENDAR_ID_PATH):
41 """取得指定行事曆的資訊(不含事件)。"""
42 commit_hash, calendar = await get_runtime().calendars.get_calendar(calendar_id)
43 if calendar is None:
44 raise HTTPException(status_code=404, detail="找不到行事曆")
45 if commit_hash is not None:
46 response.headers["X-Data-Commit-Hash"] = commit_hash
47 return calendar
50@router.get(
51 "/{calendar_id}/events",
52 response_model=list[CalendarEvent],
53 operation_id="searchCalendarEvents",
54 responses={
55 200: {"headers": COUNT_HEADERS},
56 400: {"model": ErrorResponse, "description": "Start date must not follow end date"},
57 404: {"model": ErrorResponse, "description": "Calendar not found"},
58 },
59)
60async def search_calendar_events(
61 response: Response,
62 calendar_id: str = CALENDAR_ID_PATH,
63 start: Optional[date] = Query(
64 None, description="只回傳在此日期(含)之後仍進行中的事件,格式 YYYY-MM-DD"
65 ),
66 end: Optional[date] = Query(
67 None, description="只回傳在此日期(含)之前開始的事件,格式 YYYY-MM-DD"
68 ),
69 keyword: Optional[str] = Query(
70 None, description="在標題與說明中搜尋的關鍵字,不區分大小寫,不使用正規表示式"
71 ),
72 limit: int = Query(100, ge=1, le=1000, description="最多回傳筆數"),
73 offset: int = Query(0, ge=0, description="略過的筆數,用於分頁"),
74):
75 """
76 查詢與指定日期區間重疊的事件,依開始時間、事件 id 排序。
78 日期採用來源行事曆的當地日期,區間包含首尾兩日,可省略任一邊界。
79 全天事件的結束日期不包含在內;恰好於午夜結束的事件不涵蓋隔日。
80 所有篩選條件以 AND 結合,分頁前的符合筆數放在 `X-Total-Count` header。
82 例如:`/calendars/academic/events?start=2026-10-01&end=2026-10-31`
83 """
84 if start and end and start > end:
85 raise HTTPException(status_code=400, detail="start 不可晚於 end")
86 commit_hash, events = await get_runtime().calendars.search_calendar_events(
87 calendar_id, start=start, end=end, keyword=keyword
88 )
89 if events is None:
90 raise HTTPException(status_code=404, detail="找不到行事曆")
91 if commit_hash is not None:
92 response.headers["X-Data-Commit-Hash"] = commit_hash
93 response.headers["X-Total-Count"] = str(len(events))
94 return events[offset : offset + limit]
97@router.get(
98 "/{calendar_id}/events/{event_id}",
99 response_model=CalendarEvent,
100 operation_id="getCalendarEvent",
101 responses={404: {"model": ErrorResponse, "description": "Calendar event not found"}},
102)
103async def get_calendar_event(
104 response: Response,
105 calendar_id: str = CALENDAR_ID_PATH,
106 event_id: str = Path(..., description="事件 id"),
107):
108 """取得指定行事曆中的單一事件。"""
109 commit_hash, event = await get_runtime().calendars.get_calendar_event(calendar_id, event_id)
110 if event is None:
111 raise HTTPException(status_code=404, detail="找不到事件")
112 if commit_hash is not None:
113 response.headers["X-Data-Commit-Hash"] = commit_hash
114 return event