feat: Mongo-backed Pods with full CRUD + member management

Pods are persisted in MongoDB and CRUD'd via /api/pods (list/create/get/
update/delete + add/remove member). Shared Pod reshaped to a persisted entity
(members: string[], description, timestamps) + PodInput. Backend seeds Demo/
Frontend/Backend Pods on first run (Squad -> Pod rename). Frontend replaces
hardcoded teams with live data: PodCard (member chips add/remove, inline edit,
delete, join) + CreatePodForm; App fetches+mutates via API. Removes teams.ts/
TeamCard; gitignores test artifacts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kartikeya
2026-06-27 15:48:55 -07:00
parent 789f0da61b
commit 5a03ad4d6b
13 changed files with 708 additions and 148 deletions
+62 -3
View File
@@ -1,7 +1,15 @@
import type { InterventionOutcome } from '@podman/shared';
import type { InterventionOutcome, Pod, PodInput } from '@podman/shared';
const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8787';
async function json<T>(res: Response): Promise<T> {
if (!res.ok) {
const body = (await res.json().catch(() => ({}))) as { error?: string };
throw new Error(body.error || `request failed: ${res.status}`);
}
return res.json() as Promise<T>;
}
/** Mint a LiveKit token from the backend. */
export async function fetchToken(params: {
room: string;
@@ -14,8 +22,7 @@ export async function fetchToken(params: {
headers: { 'content-type': 'application/json' },
body: JSON.stringify(params),
});
if (!res.ok) throw new Error(`token request failed: ${res.status}`);
return res.json() as Promise<{ token: string; url: string }>;
return json(res);
}
/** Record an intervention outcome for the policy learning loop. */
@@ -27,3 +34,55 @@ export async function postOutcome(outcome: InterventionOutcome): Promise<void> {
});
if (!res.ok) throw new Error(`outcome post failed: ${res.status}`);
}
// --- Pods CRUD ---
export async function listPods(): Promise<Pod[]> {
return json(await fetch(`${BACKEND_URL}/api/pods`));
}
export async function createPod(input: PodInput): Promise<Pod> {
return json(
await fetch(`${BACKEND_URL}/api/pods`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(input),
}),
);
}
export async function updatePod(id: string, patch: PodInput): Promise<Pod> {
return json(
await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}`, {
method: 'PATCH',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(patch),
}),
);
}
export async function deletePod(id: string): Promise<void> {
const res = await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error(`delete pod failed: ${res.status}`);
}
export async function addMember(id: string, name: string): Promise<Pod> {
return json(
await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/members`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name }),
}),
);
}
export async function removeMember(id: string, name: string): Promise<Pod> {
return json(
await fetch(
`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/members/${encodeURIComponent(name)}`,
{ method: 'DELETE' },
),
);
}