mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 20:51:17 +00:00
95 lines
3.6 KiB
TypeScript
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,
|
|
}
|
|
})
|
|
}
|