since3-portal/modules/shared/queries/info.ts
2026-09-29 15:20:50 +02:00

95 lines
3.6 KiB
TypeScript

import "server-only"
import { createClient } from "@/lib/supabase/server"
import { AUTO_CHECK_STALE_AFTER_MS, type InternalNote, type Product, type Task } from "@/modules/shared/schemas/info"
import { toCheckableUrl } from "@/modules/shared/lib/reachability"
export async function getProducts(clientId: string): Promise<Product[]> {
const supabase = await createClient()
const { data, error } = await supabase
.from("client_products")
.select("id, name, category, url, status, status_message, uptime_30d, updated_at")
.eq("client_id", clientId)
.order("created_at")
if (error) throw new Error("Produkte konnten nicht geladen werden.")
if (data.length === 0) return []
const { data: summaries, error: summaryError } = await supabase
.from("product_check_summary")
.select("product_id, checked_at, ok, response_ms, error, checks_30d, uptime_30d")
.in("product_id", data.map((row) => row.id))
if (summaryError) throw new Error("Prüfergebnisse konnten nicht geladen werden.")
const summaryById = new Map(summaries.map((row) => [row.product_id as string, row]))
const now = Date.now()
return data.map((row) => {
const summary = summaryById.get(row.id)
const checkable = toCheckableUrl(row.url) !== null
return {
id: row.id,
name: row.name,
category: row.category,
url: row.url,
status: row.status,
statusMessage: row.status_message,
uptime30d: row.uptime_30d === null ? null : Number(row.uptime_30d),
updatedAt: row.updated_at,
checkable,
checkDue: checkable && (!summary?.checked_at || now - Date.parse(summary.checked_at) > AUTO_CHECK_STALE_AFTER_MS),
measuredUptime30d: summary?.uptime_30d == null ? null : Number(summary.uptime_30d),
checks30d: summary?.checks_30d ?? 0,
lastCheck: summary?.checked_at
? {
checkedAt: summary.checked_at,
ok: summary.ok,
responseMs: summary.response_ms,
error: summary.error,
}
: null,
}
})
}
export async function getOpenTasks(clientId: string): Promise<Task[]> {
const supabase = await createClient()
const { data, error } = await supabase
.from("product_tasks")
.select("id, name, description, status, since")
.eq("client_id", clientId)
.in("status", ["in_progress", "planned"])
.order("status")
.order("since")
if (error) throw new Error("Vorgänge konnten nicht geladen werden.")
return data.map((row) => ({
id: row.id,
name: row.name,
description: row.description,
status: row.status,
since: row.since,
}))
}
export async function getInternalNotes(clientId: string): Promise<InternalNote[]> {
const supabase = await createClient()
const { data, error } = await supabase
.from("internal_notes")
.select("id, body, updated_at, editor:profiles!internal_notes_updated_by_fkey(name)")
.eq("client_id", clientId)
.order("updated_at", { ascending: false })
if (error) throw new Error("Interne Notizen konnten nicht geladen werden.")
return data.map((row) => {
const editor = row.editor as { name: string } | { name: string }[] | null
return {
id: row.id,
body: row.body,
updatedAt: row.updated_at,
editorName: (Array.isArray(editor) ? editor[0]?.name : editor?.name) ?? null,
}
})
}