chore: sync staging

This commit is contained in:
Esdras Renan 2025-11-10 01:57:45 -03:00
parent c5ddd54a3e
commit 561b19cf66
610 changed files with 105285 additions and 1206 deletions

View file

@ -0,0 +1,149 @@
"use server"
import { addMonths, addWeeks, isBefore } from "date-fns"
import type { ReportExportRun, ReportExportSchedule } from "@prisma/client"
import { REPORT_EXPORT_DEFINITIONS, type ReportExportKey } from "@/lib/report-definitions"
type SerializableSchedule = ReportExportSchedule & {
reportKeys: ReportExportKey[] | null
recipients: string[] | null
}
export function sanitizeReportKeys(keys: string[]): ReportExportKey[] {
return Array.from(
new Set(
keys
.map((key) => key as ReportExportKey)
.filter((key): key is ReportExportKey => Boolean(REPORT_EXPORT_DEFINITIONS[key]))
)
)
}
export function sanitizeRecipients(recipients: string[]): string[] {
return Array.from(
new Set(
recipients
.map((value) => value.trim().toLowerCase())
.filter((value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
)
)
}
type NextRunInput = {
frequency: "daily" | "weekly" | "monthly"
dayOfWeek?: number | null
dayOfMonth?: number | null
hour: number
minute: number
timezone: string
}
export function computeNextRunAt({ frequency, dayOfWeek, dayOfMonth, hour, minute, timezone }: NextRunInput, currentDate = new Date()): Date {
const base = zonedDate(currentDate, timezone)
let candidate = new Date(Date.UTC(base.getUTCFullYear(), base.getUTCMonth(), base.getUTCDate(), hour, minute, 0, 0))
if (frequency === "daily") {
if (!isBefore(base, candidate)) {
candidate.setUTCDate(candidate.getUTCDate() + 1)
}
return candidate
}
if (frequency === "weekly") {
const targetDow = typeof dayOfWeek === "number" ? clamp(dayOfWeek, 0, 6) : 1
while (candidate.getUTCDay() !== targetDow || !isBefore(base, candidate)) {
candidate = addWeeks(candidate, 1)
}
return candidate
}
// monthly
const targetDay = typeof dayOfMonth === "number" ? clamp(dayOfMonth, 1, 28) : 1
candidate.setUTCDate(targetDay)
if (!isBefore(base, candidate)) {
candidate = addMonths(candidate, 1)
candidate.setUTCDate(Math.min(targetDay, daysInMonth(candidate)))
}
return candidate
}
function clamp(value: number, min: number, max: number) {
return Math.max(min, Math.min(max, value))
}
function daysInMonth(date: Date) {
const year = date.getUTCFullYear()
const month = date.getUTCMonth()
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
}
function zonedDate(date: Date, timeZone: string) {
const formatter = new Intl.DateTimeFormat("en-US", {
timeZone,
hour12: false,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})
const parts = formatter.formatToParts(date)
const get = (type: Intl.DateTimeFormatPartTypes) =>
Number(parts.find((part) => part.type === type)?.value ?? "0")
const year = get("year")
const month = get("month")
const day = get("day")
const hour = get("hour")
const minute = get("minute")
const second = get("second")
return new Date(Date.UTC(year, month - 1, day, hour, minute, second))
}
export function serializeSchedule(
schedule: SerializableSchedule,
runs: ReportExportRun[] = []
) {
return {
id: schedule.id,
name: schedule.name,
tenantId: schedule.tenantId,
reportKeys: schedule.reportKeys ?? [],
range: schedule.range,
companyId: schedule.companyId,
companyName: schedule.companyName,
format: schedule.format,
frequency: schedule.frequency,
dayOfWeek: schedule.dayOfWeek,
dayOfMonth: schedule.dayOfMonth,
hour: schedule.hour,
minute: schedule.minute,
timezone: schedule.timezone,
recipients: schedule.recipients ?? [],
status: schedule.status,
lastRunAt: schedule.lastRunAt?.toISOString() ?? null,
nextRunAt: schedule.nextRunAt?.toISOString() ?? null,
createdAt: schedule.createdAt.toISOString(),
updatedAt: schedule.updatedAt.toISOString(),
runs: runs.map((run) => ({
id: run.id,
status: run.status,
startedAt: run.startedAt.toISOString(),
completedAt: run.completedAt?.toISOString() ?? null,
error: run.error,
artifacts: Array.isArray(run.artifacts)
? (run.artifacts as Array<Record<string, unknown>>).map((artifact, index) => {
const rawKey = artifact["key"]
const rawName = artifact["fileName"]
const rawMime = artifact["mimeType"]
return {
index,
key: typeof rawKey === "string" ? rawKey : `arquivo-${index + 1}`,
fileName: typeof rawName === "string" ? rawName : null,
mimeType: typeof rawMime === "string" ? rawMime : null,
}
})
: [],
})),
}
}