Add backend/ directory with Fastify 5 + TypeScript ESM service: - Modules: timers, routines, households, shared-timers, webhooks (migrated from platform-service) - Cosmos containers: timers, routines, households, shared_timers, webhook_subscriptions, webhook_events - JWT verification via jose (matches platform-service issuer) - Shared @bytelyst/* packages via file: refs - 171 Vitest tests passing Update AGENTS.md: update backend integration section with product backend details
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
/**
|
|
* Shared timers repository — Cosmos DB CRUD for household shared timers.
|
|
*
|
|
* Container: shared_timers (partition key: /householdId)
|
|
*/
|
|
|
|
import { getContainer } from '../../lib/cosmos.js';
|
|
import type { SharedTimerDoc, SharedTimerQuery } from './types.js';
|
|
|
|
function container() {
|
|
return getContainer('shared_timers');
|
|
}
|
|
|
|
export async function listSharedTimers(
|
|
householdId: string,
|
|
productId: string,
|
|
query: SharedTimerQuery
|
|
): Promise<{ items: SharedTimerDoc[]; total: number }> {
|
|
const conditions: string[] = ['c.householdId = @householdId', 'c.productId = @productId'];
|
|
const params: { name: string; value: string | number }[] = [
|
|
{ name: '@householdId', value: householdId },
|
|
{ name: '@productId', value: productId },
|
|
];
|
|
|
|
if (query.state) {
|
|
conditions.push('c.state = @state');
|
|
params.push({ name: '@state', value: query.state });
|
|
}
|
|
if (query.type) {
|
|
conditions.push('c.type = @type');
|
|
params.push({ name: '@type', value: query.type });
|
|
}
|
|
|
|
const where = `WHERE ${conditions.join(' AND ')}`;
|
|
const sortField = `c.${query.sortBy}`;
|
|
const orderDir = query.sortOrder.toUpperCase();
|
|
|
|
const countResult = await container()
|
|
.items.query<number>({
|
|
query: `SELECT VALUE COUNT(1) FROM c ${where}`,
|
|
parameters: params,
|
|
})
|
|
.fetchAll();
|
|
const total = countResult.resources[0] ?? 0;
|
|
|
|
const { resources } = await container()
|
|
.items.query<SharedTimerDoc>({
|
|
query: `SELECT * FROM c ${where} ORDER BY ${sortField} ${orderDir} OFFSET @offset LIMIT @limit`,
|
|
parameters: [
|
|
...params,
|
|
{ name: '@offset', value: query.offset },
|
|
{ name: '@limit', value: query.limit },
|
|
],
|
|
})
|
|
.fetchAll();
|
|
|
|
return { items: resources, total };
|
|
}
|
|
|
|
export async function getSharedTimer(
|
|
id: string,
|
|
householdId: string
|
|
): Promise<SharedTimerDoc | null> {
|
|
try {
|
|
const { resource } = await container().item(id, householdId).read<SharedTimerDoc>();
|
|
return resource ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function createSharedTimer(doc: SharedTimerDoc): Promise<SharedTimerDoc> {
|
|
const { resource } = await container().items.create(doc);
|
|
return resource as SharedTimerDoc;
|
|
}
|
|
|
|
export async function replaceSharedTimer(doc: SharedTimerDoc): Promise<SharedTimerDoc> {
|
|
const { resource } = await container().item(doc.id, doc.householdId).replace(doc);
|
|
return resource as SharedTimerDoc;
|
|
}
|
|
|
|
export async function deleteSharedTimer(id: string, householdId: string): Promise<boolean> {
|
|
try {
|
|
const existing = await getSharedTimer(id, householdId);
|
|
if (!existing) return false;
|
|
await container().item(id, householdId).delete();
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|