43 lines
1.6 KiB
TypeScript
43 lines
1.6 KiB
TypeScript
import { Router } from 'express';
|
||
import type { Repository } from '../db/repository.js';
|
||
import { spaceViewerOf, parseTzOffset, monthBounds } from './space-api.js';
|
||
|
||
// 横断(全スペース)カレンダー API。
|
||
// spec: docs/superpowers/specs/2026-06-19-space-calendar-design.md §V2
|
||
//
|
||
// GET /api/calendar/cross?month=YYYY-MM&tz_offset=<min>
|
||
// → { days: {[date]: {[spaceId]: {taskCount,eventCount}}}, events, spaces }
|
||
//
|
||
// 可視性は Repository.getCrossSpaceCalendarMonth が listSpaces(buildVisibilityWhere)
|
||
// 経由で絞るので、viewer が見られないスペースのカウント・イベントは一切返さない。
|
||
// 認可ミドルウェア(requireAuth / no-auth フォールバック)はマウント側(server.ts)で
|
||
// 他のトップレベル API と同じパターンを付与する。
|
||
|
||
export interface CrossCalendarApiDeps {
|
||
repo: Repository;
|
||
authActive: boolean;
|
||
}
|
||
|
||
export function createCrossCalendarApi(deps: CrossCalendarApiDeps): Router {
|
||
const router = Router();
|
||
const { repo } = deps;
|
||
|
||
router.get('/cross', async (req, res) => {
|
||
const viewer = spaceViewerOf(req, deps.authActive);
|
||
const month = String(req.query.month ?? '');
|
||
if (!/^\d{4}-\d{2}$/.test(month)) {
|
||
return res.status(400).json({ error: 'month must be YYYY-MM' });
|
||
}
|
||
const tzOffsetMin = parseTzOffset(req.query.tz_offset);
|
||
const { monthStart, monthEnd } = monthBounds(month);
|
||
const result = await repo.getCrossSpaceCalendarMonth(viewer, {
|
||
monthStart,
|
||
monthEnd,
|
||
tzOffsetMin,
|
||
});
|
||
res.json(result);
|
||
});
|
||
|
||
return router;
|
||
}
|