feat(cms): improve catalog, editorial recovery and operations
CI / check (push) Successful in 52s
CI / deploy (push) Successful in 2m10s
CI / publish-container (push) Failing after 1m18s

This commit is contained in:
Simo committed 2026-09-09 19:36:15 +02:00
1 parent 2fead134e6
commit c389c3893d
122 files changed
+8137 -703

No files matched your search

+49 -21
View File
@@ -1,5 +1,7 @@
import { count, desc, inArray, like, or } from "drizzle-orm";
import { and, count, desc, eq, gte, inArray, like, lt, or } from "drizzle-orm";
import { AdminAuditLog, db, User } from "@/lib/db";
import { readAuditChanges } from "./audit-diff";
import { type AuditFilters, normalizeAuditFilters } from "./audit-filters";
interface AuditEntry {
userId: number;
@@ -68,23 +70,32 @@ export async function logAudit(entry: AuditEntry): Promise<void> {
});
}
interface GetLogsOptions {
search?: string;
page?: number;
perPage?: number;
}
export async function getAuditLogs(options: GetLogsOptions = {}) {
const { search, page = 1, perPage = 20 } = options;
export async function getAuditLogs(options: AuditFilters = {}) {
const { search, actor, action, from, until, page, perPage } =
normalizeAuditFilters(options);
const skip = (page - 1) * perPage;
const where = search
? or(
like(AdminAuditLog.action, `%${search}%`),
like(AdminAuditLog.target, `%${search}%`),
)
: undefined;
const where = and(
search
? or(
like(AdminAuditLog.action, `%${search}%`),
like(AdminAuditLog.target, `%${search}%`),
)
: undefined,
action ? eq(AdminAuditLog.action, action) : undefined,
actor
? /^[1-9]\d*$/.test(actor) && Number.isSafeInteger(Number(actor))
? eq(AdminAuditLog.userId, Number(actor))
: inArray(
AdminAuditLog.userId,
db
.select({ id: User.id })
.from(User)
.where(eq(User.username, actor)),
)
: undefined,
from ? gte(AdminAuditLog.createdAt, from) : undefined,
until ? lt(AdminAuditLog.createdAt, until) : undefined,
);
const [rows, totalResult] = await Promise.all([
db
.select({
@@ -94,6 +105,8 @@ export async function getAuditLogs(options: GetLogsOptions = {}) {
target: AdminAuditLog.target,
targetId: AdminAuditLog.targetId,
diff: AdminAuditLog.diff,
before: AdminAuditLog.before,
after: AdminAuditLog.after,
createdAt: AdminAuditLog.createdAt,
})
.from(AdminAuditLog)
@@ -116,10 +129,25 @@ export async function getAuditLogs(options: GetLogsOptions = {}) {
: [];
const userMap = new Map(users.map((u) => [u.id, u.username]));
const enrichedRows = rows.map((r) => ({
...r,
username: userMap.get(r.userId) ?? `User #${r.userId}`,
}));
const enrichedRows = rows.map((r) => {
const details = readAuditChanges(r.diff, r.before, r.after);
return {
...r,
// Only sanitized changes may cross the server/client boundary.
before: null,
after: null,
diff: details.invalid
? "[Unavailable legacy details]"
: details.changes.length
? JSON.stringify(
Object.fromEntries(
details.changes.map(({ key, from, to }) => [key, { from, to }]),
),
)
: null,
username: userMap.get(r.userId) ?? `User #${r.userId}`,
};
});
return {
rows: enrichedRows,