diff --git a/drb-c2-core/app/routers/calls.py b/drb-c2-core/app/routers/calls.py index 03caab4..f1ad4c4 100644 --- a/drb-c2-core/app/routers/calls.py +++ b/drb-c2-core/app/routers/calls.py @@ -5,6 +5,7 @@ from typing import Optional from app.internal import firestore as fstore from app.internal.auth import ( require_admin_token, + require_firebase_token, require_service_or_firebase_token, resolve_caller_org_id, reprocess_limiter, @@ -54,7 +55,7 @@ async def search_calls( link: str = Query("any", pattern="^(any|orphan|linked)$"), transcript: str = Query("any", pattern="^(any|yes|no)$"), q: Optional[str] = Query(None, description="case-insensitive substring of the transcript"), - decoded: dict = Depends(require_admin_token), + decoded: dict = Depends(require_firebase_token), ): """ Paged, filterable call archive — the backend for the /calls page. @@ -72,6 +73,11 @@ async def search_calls( `window_exhausted` says the scan hit its cap before filling the page, so an empty result means "not in this window", not "none exist". + + Open to every org member (viewer included), not just admins: the Firestore + rules already let any member read every call doc in their org + (firestore.rules `calls` → docInMyOrg), so this route exposes nothing a + viewer's browser couldn't already read directly. """ org_id = await resolve_caller_org_id(decoded) if org_id is None: diff --git a/drb-frontend/app/calls/page.tsx b/drb-frontend/app/calls/page.tsx index 29c528e..c4996e3 100644 --- a/drb-frontend/app/calls/page.tsx +++ b/drb-frontend/app/calls/page.tsx @@ -6,8 +6,9 @@ // never correlated was invisible. That is the wrong way round when correlation // quality is the thing under development — the orphans are the evidence. // -// Admin-only, because it exposes every call in the org regardless of node -// ownership and carries the manual attribution controls. +// Readable by every org member — the Firestore rules already let any member +// read every call in their org. The manual attribution controls stay +// admin-only, matching the admin gate on the link/unlink routes. import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; @@ -68,11 +69,13 @@ function ArchiveRow({ call, systemName, incidents, + canEdit, onChanged, }: { call: CallRecord; systemName?: string; incidents: IncidentRecord[]; + canEdit: boolean; onChanged: () => void; }) { const [open, setOpen] = useState(false); @@ -178,18 +181,18 @@ function ArchiveRow({
attached to {inc?.title ?? id.slice(0, 8)} - + }
); })} -
+ {canEdit &&
setSearch(e.target.value)} + placeholder="Search title, location, units…" + className="bg-surface border border-line rounded-lg text-sm text-ink px-3 py-2 w-full sm:w-64 focus:outline-none focus:border-accent" + /> +
+ +
+ + +