zerobyte/app/server/utils/retention-categories.ts
Nico 09c1cbbb94
feat: show retention tags in snapshots timeline (#462)
* feat: show retention tags in snapshots timeline

* refactor: use button for accesibility
2026-02-08 16:14:43 +01:00

54 lines
1.5 KiB
TypeScript

import { format } from "date-fns";
import type { RetentionPolicy } from "../modules/backups/backups.dto";
export type RetentionCategory = "latest" | "hourly" | "daily" | "weekly" | "monthly" | "yearly";
interface SnapshotInfo {
short_id: string;
time: number;
}
const RETENTION_RULES = [
{ prop: "keepHourly", tag: "hourly", fmt: "yyyy-MM-dd-HH" },
{ prop: "keepDaily", tag: "daily", fmt: "yyyy-MM-dd" },
{ prop: "keepWeekly", tag: "weekly", fmt: "RRRR-'W'II" },
{ prop: "keepMonthly", tag: "monthly", fmt: "yyyy-MM" },
{ prop: "keepYearly", tag: "yearly", fmt: "yyyy" },
] as const;
export const computeRetentionCategories = (snapshots: SnapshotInfo[], policy: RetentionPolicy | null) => {
const categories = new Map<string, RetentionCategory[]>();
if (snapshots.length === 0) return categories;
const sorted = [...snapshots].sort((a, b) => b.time - a.time);
const addTag = (id: string, tag: RetentionCategory) => {
const tags = categories.get(id) ?? [];
if (!tags.includes(tag)) categories.set(id, [...tags, tag]);
};
addTag(sorted[0].short_id, "latest");
if (!policy) return categories;
for (const { prop, tag, fmt } of RETENTION_RULES) {
const limit = policy[prop];
if (!limit || limit <= 0) continue;
const seenBuckets = new Set<string>();
let count = 0;
for (const snapshot of sorted) {
if (count >= limit) break;
const bucketKey = format(snapshot.time, fmt);
if (!seenBuckets.has(bucketKey)) {
seenBuckets.add(bucketKey);
addTag(snapshot.short_id, tag);
count++;
}
}
}
return categories;
};