/**
* GET /facilities — 施設ロボットマスタ(F-08。読み取り専用)
*
* 形は `app/api/ds-devices/route.ts` と同じ。
* 仕様の正は `d-watch-api.yaml` の /facilities。
*
* ★登録・更新は D-Security 側の画面で行うため、D-Watch は GET のみ★(YAMLの description)。
* ★summary は無い(items と meta だけ)★ — この画面は台帳の閲覧で、
* /ds-devices のような「異常◯件」のサマリーカードを持たない。
*/
import { NextResponse } from "next/server";
import { db } from "@/lib/db";
import { filterFacilitiesByIds } from "@/lib/facilities/filter-by-ids";
import { resolveFacilityIdsByMasters } from "@/lib/facilities/resolve-by-master";
import {
filterFacilitiesByFields,
filterFacilitiesByQ,
paginateFacilities,
parseFacilityQuery,
sortFacilities,
} from "@/lib/facilities/query";
import { withRequestId } from "@/lib/logger";
import { logApiError } from "@/lib/logger/api";
import type { ApiError, Facility, ListResponse } from "@/lib/types";
const ROUTE = "/api/facilities";
function errorResponse(error: ApiError, status: number) {
return NextResponse.json(error, { status });
}
export async function GET(request: Request) {
return withRequestId(request, async () => {
const { searchParams } = new URL(request.url);
// ★422 分岐が付いた(指示書AK 段階2)★ — `group_id` / `customer_id` が
// このエンドポイント初の「不正値がありうるパラメータ」になった
// (理由は `parseFacilityQuery` の JSDoc。YAML の /facilities にも 422 を追記済み)。
const parsed = parseFacilityQuery(searchParams);
if (!parsed.ok) return errorResponse(parsed.error, parsed.status);
const query = parsed.query;
let rows: Facility[];
// ★分類(`group_id` / `customer_id`)→ 施設IDの配列★(指示書AK 段階4)。
// ★`null` = 絞らない / `[]` = 0件★(★罠1★。`?.length` で分岐しないこと)。
let facilityIds: number[] | null;
try {
// ★解決(施設マスタの読み取り)も同じ try に入れる★ — 失敗したら 500。
// ★黙って絞らずに返さないこと★(他社の施設が混ざったまま台帳に出る)。
facilityIds = await resolveFacilityIdsByMasters(parsed.query);
rows = await db.facilities();
} catch (e) {
const body: ApiError = {
error: { code: "internal_error", message: "データの取得に失敗しました", details: null },
};
logApiError({ route: ROUTE, method: "GET", body, status: 500, err: e });
return errorResponse(body, 500);
}
// ここから下は純粋な絞り込み・並べ替え・ページング。すべて try の外
// (`app/api/ds-devices/route.ts` と同じ方針)。
// q と個別パラメータは AND なので、順に通すだけでよい(OR は q の内部だけ)。
// ★★分類は `Facility.id` で突き合わせる★★(指示書AK 段階4)—
// ★このAPIが返すのは「施設の行そのもの」★なので、
// `DsDevice` / `Schedule` のような `facility_id` 列を持たない。
// ★`Facility` は段階5 で `customer_id` / `group_id` を持ったが、
// 解決の入口は `resolveFacilityIdsByMasters()` のままにしてある★ —
// 施設名の解決と同じ「施設IDの配列」で積集合を取れる形を崩さないため(§2-2)。
const filtered = filterFacilitiesByFields(
filterFacilitiesByQ(filterFacilitiesByIds(rows, facilityIds), query.q),
query
);
const sorted = sortFacilities(filtered);
const body: ListResponse<Facility> = paginateFacilities(
sorted,
query.page,
query.perPage
);
return NextResponse.json(body);
});
}