feat(housekeeping): cut over administration to ase

This commit is contained in:
Simo committed 2026-08-30 20:35:22 +02:00
1 parent c9e35cf602
commit 2b8f73a91d
457 files changed
+589 -55936

No files matched your search

File diff suppressed because it is too large. Load diff
+1 -4
View File
@@ -139,9 +139,6 @@ describe("requireStaffRateLimited", () => {
vi.mocked(clientIp).mockResolvedValue("1.2.3.4");
vi.mocked(rateLimit).mockResolvedValue({ ok: false });
await requireStaffRateLimited();
expect(redirectSafe).toHaveBeenCalledWith(
"/admin?error=ratelimit",
"/admin",
);
expect(redirectSafe).toHaveBeenCalledWith("/ase?error=ratelimit", "/ase");
});
});
+6 -6
View File
@@ -21,7 +21,7 @@ export async function requireHousekeepingCapability(
context ?? (await getHousekeepingCapabilityContext());
const authorization = authorizeHousekeeping(capabilityContext, requirement);
if (!authorization.ok) redirectSafe("/admin", "/admin");
if (!authorization.ok) redirectSafe("/ase", "/ase");
return capabilityContext;
}
@@ -48,7 +48,7 @@ export async function requirePermission(
if (!canAccess(permissions, PERMS.ADMIN_DASHBOARD, session.user.rank))
redirectSafe("/", "/");
if (!canAccess(permissions, permission, session.user.rank))
redirectSafe("/admin", "/admin");
redirectSafe("/ase", "/ase");
return {
id: session.user.id,
rank: session.user.rank,
@@ -62,7 +62,7 @@ export async function requirePermissionRateLimited(
const staff = await requirePermission(permission);
const ip = await clientIp();
if (!(await rateLimit(`admin:${staff.id}:${ip}`, 30, 60_000)).ok)
redirectSafe("/admin?error=ratelimit", "/admin");
redirectSafe("/ase?error=ratelimit", "/ase");
return staff;
}
@@ -70,13 +70,13 @@ export async function requireStaffRateLimited(): Promise<StaffUser> {
const staff = await requireStaff();
const ip = await clientIp();
if (!(await rateLimit(`admin:${staff.id}:${ip}`, 30, 60_000)).ok)
redirectSafe("/admin?error=ratelimit", "/admin");
redirectSafe("/ase?error=ratelimit", "/ase");
return staff;
}
/**
* Mod-lite gate: needs any mod.* / moderation ACL, not admin.dashboard.
* Use for `/mod` layout so mid-ranks can access without full housekeeping.
* Retained for capability-compatible action adapters used by mid-rank staff.
*/
export async function requireMod(): Promise<StaffUser> {
const { session, permissions } = await getAdminContext();
@@ -106,6 +106,6 @@ export async function requireModPermission(
const { permissions } = await getAdminContext();
const needed = Array.isArray(permission) ? permission : [permission];
const ok = needed.some((slug) => canAccess(permissions, slug, staff.rank));
if (!ok) redirectSafe("/mod", "/mod");
if (!ok) redirectSafe("/ase", "/ase");
return staff;
}
+6 -14
View File
@@ -60,7 +60,6 @@ async function fetchCmsCandidates(
limit: number,
search: string,
openOnly: boolean,
base: "admin" | "mod",
): Promise<TicketInboxRow[]> {
const Creator = alias(User, "inbox_ticket_creator");
const conditions: SQL[] = [];
@@ -102,7 +101,7 @@ async function fetchCmsCandidates(
.limit(limit)
.catch(() => []);
const prefix = base === "mod" ? "/mod/tickets" : "/admin/tickets";
const prefix = "/ase/people/support/tickets";
return rows.map((row) => ({
key: `cms-${row.id}`,
@@ -123,7 +122,6 @@ async function fetchHelpCandidates(
limit: number,
search: string,
openOnly: boolean,
base: "admin" | "mod",
): Promise<TicketInboxRow[]> {
const conditions: SQL[] = [];
@@ -192,7 +190,7 @@ async function fetchHelpCandidates(
: [];
const usernameById = new Map(users.map((u) => [u.id, u.username]));
const prefix = base === "mod" ? "/mod/help-tickets" : "/admin/help-tickets";
const prefix = "/ase/people/support/help-tickets";
return rows.map((row) => ({
key: `help-${row.id}`,
@@ -302,7 +300,6 @@ async function fetchStrictUnifiedTicketInbox(
numericCandidate > 0
? numericCandidate
: null;
const base = options.base ?? "admin";
if (
!Number.isFinite(options.perPage) ||
!Number.isFinite(options.page) ||
@@ -441,12 +438,8 @@ async function fetchStrictUnifiedTicketInbox(
}
const prefix =
row.kind === "cms"
? base === "mod"
? "/mod/tickets"
: "/admin/tickets"
: base === "mod"
? "/mod/help-tickets"
: "/admin/help-tickets";
? "/ase/people/support/tickets"
: "/ase/people/support/help-tickets";
const dateValue =
row.dateValue === null
? null
@@ -497,7 +490,6 @@ export async function fetchUnifiedTicketInbox(
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";
@@ -513,10 +505,10 @@ export async function fetchUnifiedTicketInbox(
const [cmsRows, helpRows] = await Promise.all([
includeCms
? fetchCmsCandidates(window, search, openOnly, base)
? fetchCmsCandidates(window, search, openOnly)
: Promise.resolve([]),
includeHelp
? fetchHelpCandidates(window, search, openOnly, base)
? fetchHelpCandidates(window, search, openOnly)
: Promise.resolve([]),
]);