100 lines
3.6 KiB
TypeScript
100 lines
3.6 KiB
TypeScript
import type { ModuleDatabase } from "@ekklesia/module-sdk";
|
|
import { asc, eq } from "drizzle-orm";
|
|
import { viewers } from "../../schema/tables.js";
|
|
import type { NewStoredViewer, SignageViewerStore, StoredViewer, ViewerChanges } from "./types.js";
|
|
|
|
/** The viewer half of Signage's production store. Deliberately dull: heartbeat coalescing, health
|
|
* derivation, and every other decision live above it. */
|
|
|
|
function toViewer(row: typeof viewers.$inferSelect): StoredViewer {
|
|
return {
|
|
cachedAssetCount: row.cachedAssetCount,
|
|
channelId: row.channelId,
|
|
createdAt: row.createdAt,
|
|
deviceId: row.deviceId,
|
|
id: row.id,
|
|
lastError:
|
|
row.lastErrorCode === null || row.lastErrorAt === null
|
|
? null
|
|
: { at: row.lastErrorAt, code: row.lastErrorCode, detail: row.lastErrorDetail },
|
|
lastSeenAt: row.lastSeenAt,
|
|
name: row.name,
|
|
offlineReady: row.offlineReady,
|
|
reportedContributionId: row.reportedContributionId,
|
|
reportedPlaybackRevision: row.reportedPlaybackRevision,
|
|
reportedProviderId: row.reportedProviderId,
|
|
reportedSelectionId: row.reportedSelectionId,
|
|
reportedTimelineId: row.reportedTimelineId,
|
|
viewerKey: row.viewerKey,
|
|
};
|
|
}
|
|
|
|
export function createViewerStore(database: ModuleDatabase): SignageViewerStore {
|
|
return {
|
|
async deleteViewer(viewerId: string): Promise<void> {
|
|
await database.delete(viewers).where(eq(viewers.id, viewerId));
|
|
},
|
|
|
|
async findViewer(viewerId: string): Promise<StoredViewer | undefined> {
|
|
const rows = await database.select().from(viewers).where(eq(viewers.id, viewerId));
|
|
const row = rows[0];
|
|
return row ? toViewer(row) : undefined;
|
|
},
|
|
|
|
async findViewerByKey(viewerKey: string): Promise<StoredViewer | undefined> {
|
|
const rows = await database.select().from(viewers).where(eq(viewers.viewerKey, viewerKey));
|
|
const row = rows[0];
|
|
return row ? toViewer(row) : undefined;
|
|
},
|
|
|
|
async insertViewer(viewer: NewStoredViewer): Promise<void> {
|
|
await database.insert(viewers).values({
|
|
cachedAssetCount: viewer.cachedAssetCount,
|
|
channelId: viewer.channelId,
|
|
deviceId: viewer.deviceId,
|
|
id: viewer.id,
|
|
name: viewer.name,
|
|
offlineReady: viewer.offlineReady,
|
|
reportedContributionId: viewer.reportedContributionId,
|
|
reportedPlaybackRevision: viewer.reportedPlaybackRevision,
|
|
reportedProviderId: viewer.reportedProviderId,
|
|
reportedSelectionId: viewer.reportedSelectionId,
|
|
reportedTimelineId: viewer.reportedTimelineId,
|
|
viewerKey: viewer.viewerKey,
|
|
});
|
|
},
|
|
|
|
async listViewers(): Promise<readonly StoredViewer[]> {
|
|
const rows = await database.select().from(viewers).orderBy(asc(viewers.name));
|
|
return rows.map(toViewer);
|
|
},
|
|
|
|
async updateViewer(viewerId: string, changes: ViewerChanges): Promise<void> {
|
|
const values: Record<string, unknown> = { updatedAt: new Date() };
|
|
for (const key of [
|
|
"cachedAssetCount",
|
|
"channelId",
|
|
"deviceId",
|
|
"lastSeenAt",
|
|
"name",
|
|
"offlineReady",
|
|
"reportedContributionId",
|
|
"reportedPlaybackRevision",
|
|
"reportedProviderId",
|
|
"reportedSelectionId",
|
|
"reportedTimelineId",
|
|
] as const) {
|
|
if (changes[key] !== undefined) {
|
|
values[key] = changes[key];
|
|
}
|
|
}
|
|
if (changes.lastError !== undefined) {
|
|
values.lastErrorAt = changes.lastError?.at ?? null;
|
|
values.lastErrorCode = changes.lastError?.code ?? null;
|
|
values.lastErrorDetail = changes.lastError?.detail ?? null;
|
|
}
|
|
await database.update(viewers).set(values).where(eq(viewers.id, viewerId));
|
|
},
|
|
};
|
|
}
|