feat(hobbs-organizer): v1.4.0 per-TV morning routine (admin TVs tab) + photo app 1.2.1

- Admin TVs tab: wake time/days, YouTube Music playlist, Shield volume per TV,
  pushed via 5-min TV check-ins (/api/tv/config, /api/tv/report, LAN-only)
- com.dayline.photos 1.2.1: MorningActivity (screen on, volume, playlist),
  calendar alarm at wake time, Doze-safe chained check-ins
- Both TVs configured for 06:30 daily with the family playlist

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E9NnVEico3WvFxLyjy1iW3
This commit is contained in:
2026-11-04 22:48:50 -06:00
parent 2d16ded0c1
commit 6e94a0c998
22 changed files with 1083 additions and 18 deletions
@@ -0,0 +1,26 @@
import { clientIp, isAdminNetworkAllowed, json } from "../../../_lib/auth";
import { readSettings, validTvId } from "../../../_lib/store";
import { recordTvCheckIn, tvConfig } from "../../../_lib/tvs";
export const dynamic = "force-dynamic";
/**
* Called by the Shield photo app every few minutes:
* GET /api/tv/config?device=<android id>&name=<device name>&model=<model>&app=<app version>&applied=<config version>
* Same network rule as admin: direct LAN only, never through a proxy.
*/
export async function GET(request: Request) {
if (!isAdminNetworkAllowed(request.headers)) return new Response("Not found", { status: 404 });
const params = new URL(request.url).searchParams;
const id = validTvId(params.get("device"));
if (!id) return json({ error: "Missing or invalid device id." }, 400);
const settings = await readSettings();
const setting = settings.tvs.find((tv) => tv.id === id);
await recordTvCheckIn(
id,
{ deviceName: params.get("name"), model: params.get("model"), appVersion: params.get("app"), appliedVersion: params.get("applied"), ip: clientIp(request.headers) },
undefined,
new Set(settings.tvs.map((tv) => tv.id)),
);
return json(tvConfig(setting));
}