Spaces:
Running
Running
| import { getSupabaseClient } from '../database' | |
| import { isRenderFailureFeatureEnabled } from './config' | |
| import type { | |
| CreateRenderFailureEventInput, | |
| RenderFailureEventRow, | |
| RenderFailureListResult, | |
| RenderFailureQuery | |
| } from './types' | |
| const TABLE = 'render_failure_events' | |
| const DEFAULT_PAGE = 1 | |
| const DEFAULT_PAGE_SIZE = 20 | |
| const MAX_PAGE_SIZE = 200 | |
| const DEFAULT_EXPORT_LIMIT = 1000 | |
| const MAX_EXPORT_LIMIT = 5000 | |
| function applyBaseFilters(builder: any, query: RenderFailureQuery): any { | |
| let next = builder | |
| if (query.from) { | |
| next = next.gte('created_at', `${query.from}T00:00:00.000Z`) | |
| } | |
| if (query.to) { | |
| next = next.lte('created_at', `${query.to}T23:59:59.999Z`) | |
| } | |
| if (query.errorType) { | |
| next = next.eq('error_type', query.errorType) | |
| } | |
| if (query.outputMode) { | |
| next = next.eq('output_mode', query.outputMode) | |
| } | |
| if (query.jobId) { | |
| next = next.eq('job_id', query.jobId) | |
| } | |
| if (typeof query.recovered === 'boolean') { | |
| next = next.eq('recovered', query.recovered) | |
| } | |
| return next | |
| } | |
| export async function createRenderFailureEvent( | |
| input: CreateRenderFailureEventInput | |
| ): Promise<RenderFailureEventRow | null> { | |
| if (!isRenderFailureFeatureEnabled()) { | |
| return null | |
| } | |
| const client = getSupabaseClient() | |
| if (!client) { | |
| return null | |
| } | |
| try { | |
| const { data, error } = await client.from(TABLE).insert(input).select('*').single() | |
| if (error) { | |
| console.error('[RenderFailure] Failed to create record:', error.message) | |
| return null | |
| } | |
| return data as RenderFailureEventRow | |
| } catch (error) { | |
| console.error('[RenderFailure] Failed to create record:', error) | |
| return null | |
| } | |
| } | |
| export async function listRenderFailureEvents( | |
| query: RenderFailureQuery = {} | |
| ): Promise<RenderFailureListResult> { | |
| const page = Math.max(DEFAULT_PAGE, query.page ?? DEFAULT_PAGE) | |
| const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, query.pageSize ?? DEFAULT_PAGE_SIZE)) | |
| const empty: RenderFailureListResult = { | |
| records: [], | |
| total: 0, | |
| page, | |
| pageSize, | |
| hasMore: false | |
| } | |
| if (!isRenderFailureFeatureEnabled()) { | |
| return empty | |
| } | |
| const client = getSupabaseClient() | |
| if (!client) { | |
| return empty | |
| } | |
| const from = (page - 1) * pageSize | |
| const to = from + pageSize - 1 | |
| try { | |
| const countQuery = applyBaseFilters( | |
| client.from(TABLE).select('*', { count: 'exact', head: true }), | |
| query | |
| ) | |
| const { count, error: countError } = await countQuery | |
| if (countError) { | |
| console.error('[RenderFailure] Failed to count records:', countError.message) | |
| return empty | |
| } | |
| const dataQuery = applyBaseFilters(client.from(TABLE).select('*'), query) | |
| .order('created_at', { ascending: false }) | |
| .range(from, to) | |
| const { data, error } = await dataQuery | |
| if (error) { | |
| console.error('[RenderFailure] Failed to list records:', error.message) | |
| return empty | |
| } | |
| const records = (data ?? []) as RenderFailureEventRow[] | |
| const total = count ?? 0 | |
| return { | |
| records, | |
| total, | |
| page, | |
| pageSize, | |
| hasMore: from + records.length < total | |
| } | |
| } catch (error) { | |
| console.error('[RenderFailure] Failed to list records:', error) | |
| return empty | |
| } | |
| } | |
| export async function exportRenderFailureEvents( | |
| query: RenderFailureQuery = {} | |
| ): Promise<RenderFailureEventRow[]> { | |
| if (!isRenderFailureFeatureEnabled()) { | |
| return [] | |
| } | |
| const client = getSupabaseClient() | |
| if (!client) { | |
| return [] | |
| } | |
| const limit = Math.min(MAX_EXPORT_LIMIT, Math.max(1, query.limit ?? DEFAULT_EXPORT_LIMIT)) | |
| try { | |
| const dataQuery = applyBaseFilters(client.from(TABLE).select('*'), query) | |
| .order('created_at', { ascending: false }) | |
| .limit(limit) | |
| const { data, error } = await dataQuery | |
| if (error) { | |
| console.error('[RenderFailure] Failed to export records:', error.message) | |
| return [] | |
| } | |
| return (data ?? []) as RenderFailureEventRow[] | |
| } catch (error) { | |
| console.error('[RenderFailure] Failed to export records:', error) | |
| return [] | |
| } | |
| } | |
| export async function markRecoveredByJobId(jobId: string): Promise<boolean> { | |
| if (!isRenderFailureFeatureEnabled()) { | |
| return false | |
| } | |
| const client = getSupabaseClient() | |
| if (!client) { | |
| return false | |
| } | |
| try { | |
| const { error } = await client | |
| .from(TABLE) | |
| .update({ recovered: true }) | |
| .eq('job_id', jobId) | |
| .eq('recovered', false) | |
| if (error) { | |
| console.error('[RenderFailure] Failed to mark recovered:', error.message) | |
| return false | |
| } | |
| return true | |
| } catch (error) { | |
| console.error('[RenderFailure] Failed to mark recovered:', error) | |
| return false | |
| } | |
| } |