feat(housekeeping): cut over administration to ase
This commit is contained in:
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
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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([]),
|
||||
]);
|
||||
|
||||
|
||||
Reference in new issue
Block a user