Revert "Merge pull request 'Complete Housekeeping migration and /ase cutover' (#52) from codex/housekeeping-complete into main"
CI / check (push) Successful in 27s
CI / release (push) Skipped
CI / deploy (push) Successful in 43s

This reverts commit 488b6e57c4, reversing
changes made to b506b4499a.
This commit is contained in:
Simo committed 2026-08-30 21:31:34 +02:00
1 parent 488b6e57c4
commit b1ddda66ff
802 files changed
+61370 -76659

No files matched your search

+7 -214
View File
@@ -10,7 +10,6 @@ import {
ne,
or,
type SQL,
sql,
} from "drizzle-orm";
import { alias } from "drizzle-orm/mysql-core";
import { calcPagination } from "@/lib/admin-helpers";
@@ -37,16 +36,11 @@ export interface TicketInboxRow {
export interface FetchTicketInboxOptions {
page: number;
perPage: number;
offset?: number;
search?: string;
type?: TicketInboxTypeFilter;
/** Default true: only non-closed CMS + open help-center. */
openOnly?: boolean;
base?: "admin" | "mod";
sort?: "id" | "subject" | "status" | "updatedAt";
order?: "asc" | "desc";
/** Fail-closed, DB-paged boundary for security-sensitive read models. */
strict?: boolean;
}
function toSortMs(value: Date | string | null | undefined): number {
@@ -60,6 +54,7 @@ async function fetchCmsCandidates(
limit: number,
search: string,
openOnly: boolean,
base: "admin" | "mod",
): Promise<TicketInboxRow[]> {
const Creator = alias(User, "inbox_ticket_creator");
const conditions: SQL[] = [];
@@ -101,7 +96,7 @@ async function fetchCmsCandidates(
.limit(limit)
.catch(() => []);
const prefix = "/ase/people/support/tickets";
const prefix = base === "mod" ? "/mod/tickets" : "/admin/tickets";
return rows.map((row) => ({
key: `cms-${row.id}`,
@@ -122,6 +117,7 @@ async function fetchHelpCandidates(
limit: number,
search: string,
openOnly: boolean,
base: "admin" | "mod",
): Promise<TicketInboxRow[]> {
const conditions: SQL[] = [];
@@ -190,7 +186,7 @@ async function fetchHelpCandidates(
: [];
const usernameById = new Map(users.map((u) => [u.id, u.username]));
const prefix = "/ase/people/support/help-tickets";
const prefix = base === "mod" ? "/mod/help-tickets" : "/admin/help-tickets";
return rows.map((row) => ({
key: `help-${row.id}`,
@@ -271,209 +267,6 @@ async function countHelp(search: string, openOnly: boolean): Promise<number> {
return Number(rows[0]?.total ?? 0);
}
function strictRows<T>(result: unknown): T[] {
if (!Array.isArray(result) || !Array.isArray(result[0])) {
throw new Error("invalid ticket inbox driver result");
}
return result[0] as T[];
}
async function fetchStrictUnifiedTicketInbox(
options: FetchTicketInboxOptions,
): Promise<{
rows: TicketInboxRow[];
total: number;
page: number;
perPage: number;
lastPage: number;
cmsTotal: number;
helpTotal: number;
}> {
const type = options.type ?? "all";
const openOnly = options.openOnly !== false;
const search = options.search?.trim() ?? "";
const pattern = `%${search}%`;
const numericCandidate = /^\d+$/.test(search) ? Number(search) : null;
const numericSearch =
numericCandidate !== null &&
Number.isSafeInteger(numericCandidate) &&
numericCandidate > 0
? numericCandidate
: null;
if (
!Number.isFinite(options.perPage) ||
!Number.isFinite(options.page) ||
(options.offset !== undefined && !Number.isFinite(options.offset))
) {
throw new Error("invalid strict ticket inbox pagination");
}
const perPage = Math.min(Math.max(Math.trunc(options.perPage), 1), 100);
const requestedPage = Math.max(Math.trunc(options.page), 1);
const offset = Math.min(
Math.max(Math.trunc(options.offset ?? (requestedPage - 1) * perPage), 0),
10_000,
);
const page = Math.floor(offset / perPage) + 1;
const cmsConditions: SQL[] = [];
if (openOnly) cmsConditions.push(sql`t.status <> 'closed'`);
if (search) {
cmsConditions.push(sql`(
t.subject LIKE ${pattern}
OR t.category LIKE ${pattern}
OR creator.username LIKE ${pattern}
${numericSearch !== null ? sql`OR t.id = ${numericSearch}` : sql``}
)`);
}
const helpConditions: SQL[] = [];
if (openOnly) helpConditions.push(sql`h.open = 1`);
if (search) {
helpConditions.push(sql`(
h.title LIKE ${pattern}
OR h.content LIKE ${pattern}
OR help_user.username LIKE ${pattern}
${numericSearch !== null ? sql`OR h.id = ${numericSearch}` : sql``}
)`);
}
const cmsWhere =
cmsConditions.length > 0
? sql`WHERE ${sql.join(cmsConditions, sql` AND `)}`
: sql``;
const helpWhere =
helpConditions.length > 0
? sql`WHERE ${sql.join(helpConditions, sql` AND `)}`
: sql``;
const cmsSelect = sql`
SELECT 'cms' AS kind, t.id AS numericId, t.subject AS title,
COALESCE(creator.username, '—') AS user, t.creator_id AS userId,
t.status AS status, (t.status <> 'closed') AS open,
COALESCE(UNIX_TIMESTAMP(t.updated_at), UNIX_TIMESTAMP(t.created_at), 0) * 1000 AS sortAt,
COALESCE(t.updated_at, t.created_at) AS dateValue
FROM website_tickets t
LEFT JOIN users creator ON creator.id = t.creator_id
${cmsWhere}
`;
const helpSelect = sql`
SELECT 'help' AS kind, h.id AS numericId, h.title AS title,
COALESCE(help_user.username, '—') AS user, h.user_id AS userId,
IF(h.open = 1, 'open', 'closed') AS status, h.open AS open,
COALESCE(UNIX_TIMESTAMP(h.updated_at), UNIX_TIMESTAMP(h.created_at), 0) * 1000 AS sortAt,
COALESCE(h.updated_at, h.created_at) AS dateValue
FROM website_help_center_tickets h
LEFT JOIN users help_user ON help_user.id = h.user_id
${helpWhere}
`;
const unified =
type === "cms"
? cmsSelect
: type === "help"
? helpSelect
: sql`${cmsSelect} UNION ALL ${helpSelect}`;
const sortColumn =
options.sort === "id"
? sql`ticket_rows.numericId`
: options.sort === "subject"
? sql`ticket_rows.title`
: options.sort === "status"
? sql`ticket_rows.status`
: sql`ticket_rows.sortAt`;
const direction = options.order === "asc" ? sql`ASC` : sql`DESC`;
const [rowsResult, countResult] = await Promise.all([
db.execute(sql`
SELECT * FROM (${unified}) AS ticket_rows
ORDER BY ${sortColumn} ${direction}, ticket_rows.kind ASC,
ticket_rows.numericId ${direction}
LIMIT ${perPage} OFFSET ${offset}
`),
db.execute(sql`
SELECT COUNT(*) AS total,
SUM(ticket_rows.kind = 'cms') AS cmsTotal,
SUM(ticket_rows.kind = 'help') AS helpTotal
FROM (${unified}) AS ticket_rows
`),
]);
const counts = strictRows<{
total: number | bigint;
cmsTotal: number | bigint | null;
helpTotal: number | bigint | null;
}>(countResult)[0];
if (!counts) throw new Error("ticket inbox counts unavailable");
const total = Number(counts.total);
const cmsTotal = Number(counts.cmsTotal ?? 0);
const helpTotal = Number(counts.helpTotal ?? 0);
if (
!Number.isSafeInteger(total) ||
total < 0 ||
!Number.isSafeInteger(cmsTotal) ||
cmsTotal < 0 ||
!Number.isSafeInteger(helpTotal) ||
helpTotal < 0
) {
throw new Error("invalid ticket inbox counts");
}
const rows = strictRows<{
kind: TicketInboxKind;
numericId: number | bigint;
title: string;
user: string;
userId: number | null;
status: string;
open: number | boolean;
sortAt: number | bigint;
dateValue: Date | string | null;
}>(rowsResult).map((row) => {
const id = Number(row.numericId);
const sortAt = Number(row.sortAt);
if (
(row.kind !== "cms" && row.kind !== "help") ||
!Number.isSafeInteger(id) ||
id <= 0 ||
!Number.isSafeInteger(sortAt) ||
sortAt < 0 ||
typeof row.title !== "string" ||
typeof row.user !== "string" ||
typeof row.status !== "string"
) {
throw new Error("invalid ticket inbox row");
}
const prefix =
row.kind === "cms"
? "/ase/people/support/tickets"
: "/ase/people/support/help-tickets";
const dateValue =
row.dateValue === null
? null
: row.dateValue instanceof Date
? row.dateValue
: new Date(row.dateValue);
if (dateValue !== null && !Number.isFinite(dateValue.getTime())) {
throw new Error("invalid ticket inbox date");
}
return {
key: `${row.kind}-${id}`,
kind: row.kind,
id: String(id),
title: row.title,
user: row.user,
userId: row.userId === null ? null : Number(row.userId),
status: row.status,
open: Boolean(row.open),
sortAt,
date: formatDate(dateValue, "date"),
href: `${prefix}/${id}`,
};
});
return {
rows,
total,
page,
perPage,
lastPage: Math.max(1, Math.ceil(total / perPage)),
cmsTotal,
helpTotal,
};
}
/** Merged read-model over CMS desk + help-center queues (no DB merge). */
export async function fetchUnifiedTicketInbox(
options: FetchTicketInboxOptions,
@@ -486,10 +279,10 @@ export async function fetchUnifiedTicketInbox(
cmsTotal: number;
helpTotal: number;
}> {
if (options.strict) return fetchStrictUnifiedTicketInbox(options);
const type = options.type ?? "all";
const openOnly = options.openOnly !== false;
const search = options.search?.trim() ?? "";
const base = options.base ?? "admin";
const includeCms = type === "all" || type === "cms";
const includeHelp = type === "all" || type === "help";
@@ -505,10 +298,10 @@ export async function fetchUnifiedTicketInbox(
const [cmsRows, helpRows] = await Promise.all([
includeCms
? fetchCmsCandidates(window, search, openOnly)
? fetchCmsCandidates(window, search, openOnly, base)
: Promise.resolve([]),
includeHelp
? fetchHelpCandidates(window, search, openOnly)
? fetchHelpCandidates(window, search, openOnly, base)
: Promise.resolve([]),
]);