import { eq, and, sql } from 'drizzle-orm'; import { type LibSQLDatabase } from 'drizzle-orm/libsql'; import type * as schema from '../schema/index'; import { channels, contentItems } from '../schema/index'; import type { Channel, Platform, MonitoringMode } from '../../types/index'; // ── Types ── /** Fields needed to create a new channel (auto-generated fields excluded). */ export type CreateChannelData = Omit< Channel, 'id' | 'createdAt' | 'updatedAt' | 'lastCheckedAt' | 'lastCheckStatus' | 'monitoringMode' > & { monitoringMode?: Channel['monitoringMode'] }; /** Fields that can be updated on an existing channel. */ export type UpdateChannelData = Partial< Pick >; type Db = LibSQLDatabase; // ── Repository Functions ── /** Insert a new channel and return the created row. */ export async function createChannel( db: Db, data: CreateChannelData ): Promise { const result = await db .insert(channels) .values({ name: data.name, platform: data.platform, platformId: data.platformId, url: data.url, monitoringEnabled: data.monitoringEnabled, checkInterval: data.checkInterval, imageUrl: data.imageUrl, metadata: data.metadata, formatProfileId: data.formatProfileId, monitoringMode: data.monitoringMode ?? 'all', }) .returning(); return mapRow(result[0]); } /** Get a channel by ID. Returns null if not found. */ export async function getChannelById( db: Db, id: number ): Promise { const rows = await db .select() .from(channels) .where(eq(channels.id, id)) .limit(1); return rows.length > 0 ? mapRow(rows[0]) : null; } /** Get all channels, ordered by name. */ export async function getAllChannels(db: Db): Promise { const rows = await db.select().from(channels).orderBy(channels.name); return rows.map(mapRow); } /** Get all channels with monitoring enabled. */ export async function getEnabledChannels(db: Db): Promise { const rows = await db .select() .from(channels) .where(eq(channels.monitoringEnabled, true)) .orderBy(channels.name); return rows.map(mapRow); } /** Update specific fields on a channel. Sets updatedAt to now. Returns updated row or null. */ export async function updateChannel( db: Db, id: number, data: UpdateChannelData ): Promise { const result = await db .update(channels) .set({ ...data, updatedAt: sql`(datetime('now'))`, }) .where(eq(channels.id, id)) .returning(); return result.length > 0 ? mapRow(result[0]) : null; } /** * Change a channel's monitoring mode and cascade the `monitored` flag to all existing content items. * * Cascade logic: * - 'all' or 'existing' → existing items become monitored (true) * - 'future' or 'none' → existing items become unmonitored (false) * * Also syncs `monitoringEnabled`: mode !== 'none' → enabled (per D022). * Returns updated channel or null if not found. */ export async function setMonitoringMode( db: Db, id: number, mode: MonitoringMode ): Promise { // Step 1: Cascade monitored flag to all content items for this channel const cascadeMonitored = mode === 'all' || mode === 'existing'; await db .update(contentItems) .set({ monitored: cascadeMonitored, updatedAt: sql`(datetime('now'))`, }) .where(eq(contentItems.channelId, id)); // Step 2: Update the channel's monitoringMode and monitoringEnabled const result = await db .update(channels) .set({ monitoringMode: mode, monitoringEnabled: mode !== 'none', updatedAt: sql`(datetime('now'))`, }) .where(eq(channels.id, id)) .returning(); return result.length > 0 ? mapRow(result[0]) : null; } /** Delete a channel by ID. Returns true if a row was deleted. */ export async function deleteChannel( db: Db, id: number ): Promise { const result = await db .delete(channels) .where(eq(channels.id, id)) .returning({ id: channels.id }); return result.length > 0; } /** Find a channel by platform and platformId (for duplicate detection). */ export async function getChannelByPlatformId( db: Db, platform: string, platformId: string ): Promise { const rows = await db .select() .from(channels) .where( and( eq(channels.platform, platform), eq(channels.platformId, platformId) ) ) .limit(1); return rows.length > 0 ? mapRow(rows[0]) : null; } // ── Row Mapping ── /** * Map a raw Drizzle row to the Channel domain type. * Ensures boolean and nullable fields are correctly typed. */ function mapRow(row: typeof channels.$inferSelect): Channel { return { id: row.id, name: row.name, platform: row.platform as Platform, platformId: row.platformId, url: row.url, monitoringEnabled: row.monitoringEnabled, checkInterval: row.checkInterval, imageUrl: row.imageUrl, metadata: row.metadata as Record | null, formatProfileId: row.formatProfileId, monitoringMode: (row.monitoringMode ?? 'all') as Channel['monitoringMode'], createdAt: row.createdAt, updatedAt: row.updatedAt, lastCheckedAt: row.lastCheckedAt, lastCheckStatus: row.lastCheckStatus as Channel['lastCheckStatus'], }; }