Revert "Merge pull request 'Complete Housekeeping migration and /ase cutover' (#52) from codex/housekeeping-complete into main"
This reverts commit488b6e57c4, reversing changes made tob506b4499a.
This commit is contained in:
1 parent
488b6e57c4
commit
b1ddda66ff
802 files changed
+61370
-76659
No files matched your search
@@ -3,23 +3,12 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("ACL management contract", () => {
|
||||
it("uses normalized ACL persistence and the permissions.manage guard", () => {
|
||||
const wrapper = readFileSync("src/actions/permissions.ts", "utf8");
|
||||
const service = readFileSync(
|
||||
"src/features/housekeeping/domains/system/services/mutations.ts",
|
||||
"utf8",
|
||||
);
|
||||
expect(wrapper).toContain("PERMS.PERMISSIONS_MANAGE");
|
||||
expect(wrapper).toContain("adminAction");
|
||||
expect(wrapper).toContain("access.permissions.update");
|
||||
expect(wrapper).toContain("access.permissions.repair");
|
||||
expect(service).toContain('import "server-only"');
|
||||
expect(service).toContain("AclModelPermission");
|
||||
expect(service).not.toContain(
|
||||
"export const systemProductionMutationAdapter",
|
||||
);
|
||||
expect(service).not.toContain("legacyMessage");
|
||||
expect(service).not.toContain("websiteHousekeepingPermissions");
|
||||
expect(service).not.toContain("websiteTeams");
|
||||
const source = readFileSync("src/actions/permissions.ts", "utf8");
|
||||
expect(source).toContain("PERMS.PERMISSIONS_MANAGE");
|
||||
expect(source).toContain("adminAction");
|
||||
expect(source).toContain("AclModelPermission");
|
||||
expect(source).not.toContain("websiteHousekeepingPermissions");
|
||||
expect(source).not.toContain("websiteTeams");
|
||||
});
|
||||
|
||||
it("ships an idempotent ACL completion migration", () => {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,14 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
anyCapability,
|
||||
type HousekeepingCapabilityContext,
|
||||
} from "@/features/housekeeping/foundation/contracts";
|
||||
import { redirectSafe } from "@/lib/foundation/security";
|
||||
import { canAccess, getAdminContext } from "@/lib/permissions";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
import {
|
||||
requireHousekeepingCapability,
|
||||
requireMod,
|
||||
requireModPermission,
|
||||
requirePermission,
|
||||
@@ -75,23 +70,6 @@ describe("requirePermission", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("requireHousekeepingCapability", () => {
|
||||
it("reuses an already-created capability context without reloading permissions", async () => {
|
||||
const context: HousekeepingCapabilityContext = {
|
||||
actor: { id: 1, username: "admin", rank: 0 },
|
||||
isSuperAdmin: false,
|
||||
has: (slug) => slug === "admin.users.view",
|
||||
hasAny: (...slugs) => slugs.includes("admin.users.view"),
|
||||
hasAll: (...slugs) => slugs.every((slug) => slug === "admin.users.view"),
|
||||
};
|
||||
|
||||
await expect(
|
||||
requireHousekeepingCapability(anyCapability("admin.users.view"), context),
|
||||
).resolves.toBe(context);
|
||||
expect(getAdminContext).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("requireMod", () => {
|
||||
it("allows with MOD_DASHBOARD", async () => {
|
||||
vi.mocked(getAdminContext).mockResolvedValue({
|
||||
@@ -139,6 +117,9 @@ describe("requireStaffRateLimited", () => {
|
||||
vi.mocked(clientIp).mockResolvedValue("1.2.3.4");
|
||||
vi.mocked(rateLimit).mockResolvedValue({ ok: false });
|
||||
await requireStaffRateLimited();
|
||||
expect(redirectSafe).toHaveBeenCalledWith("/ase?error=ratelimit", "/ase");
|
||||
expect(redirectSafe).toHaveBeenCalledWith(
|
||||
"/admin?error=ratelimit",
|
||||
"/admin",
|
||||
);
|
||||
});
|
||||
});
|
||||
+5
-23
@@ -1,9 +1,3 @@
|
||||
import { authorizeHousekeeping } from "@/features/housekeeping/foundation/authorization";
|
||||
import type {
|
||||
CapabilityRequirement,
|
||||
HousekeepingCapabilityContext,
|
||||
} from "@/features/housekeeping/foundation/contracts";
|
||||
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
|
||||
import { redirectSafe } from "@/lib/foundation/security";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
@@ -13,18 +7,6 @@ export interface StaffUser {
|
||||
rank: number;
|
||||
username: string;
|
||||
}
|
||||
export async function requireHousekeepingCapability(
|
||||
requirement: CapabilityRequirement,
|
||||
context?: HousekeepingCapabilityContext,
|
||||
): Promise<HousekeepingCapabilityContext> {
|
||||
const capabilityContext =
|
||||
context ?? (await getHousekeepingCapabilityContext());
|
||||
const authorization = authorizeHousekeeping(capabilityContext, requirement);
|
||||
|
||||
if (!authorization.ok) redirectSafe("/ase", "/ase");
|
||||
|
||||
return capabilityContext;
|
||||
}
|
||||
|
||||
export async function requireStaff(): Promise<StaffUser> {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
@@ -48,7 +30,7 @@ export async function requirePermission(
|
||||
if (!canAccess(permissions, PERMS.ADMIN_DASHBOARD, session.user.rank))
|
||||
redirectSafe("/", "/");
|
||||
if (!canAccess(permissions, permission, session.user.rank))
|
||||
redirectSafe("/ase", "/ase");
|
||||
redirectSafe("/admin", "/admin");
|
||||
return {
|
||||
id: session.user.id,
|
||||
rank: session.user.rank,
|
||||
@@ -62,7 +44,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("/ase?error=ratelimit", "/ase");
|
||||
redirectSafe("/admin?error=ratelimit", "/admin");
|
||||
return staff;
|
||||
}
|
||||
|
||||
@@ -70,13 +52,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("/ase?error=ratelimit", "/ase");
|
||||
redirectSafe("/admin?error=ratelimit", "/admin");
|
||||
return staff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mod-lite gate: needs any mod.* / moderation ACL, not admin.dashboard.
|
||||
* Retained for capability-compatible action adapters used by mid-rank staff.
|
||||
* Use for `/mod` layout so mid-ranks can access without full housekeeping.
|
||||
*/
|
||||
export async function requireMod(): Promise<StaffUser> {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
@@ -106,6 +88,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("/ase", "/ase");
|
||||
if (!ok) redirectSafe("/mod", "/mod");
|
||||
return staff;
|
||||
}
|
||||
@@ -1,40 +1,41 @@
|
||||
import "server-only";
|
||||
|
||||
import { count, desc, eq } from "drizzle-orm";
|
||||
import type { OnlineUser } from "@/components/admin/dashboard";
|
||||
import { db, User } from "@/lib/db";
|
||||
|
||||
interface OpsOnlineUserRow {
|
||||
readonly id: number;
|
||||
readonly username: string;
|
||||
readonly look: string;
|
||||
readonly lastOnline: number;
|
||||
}
|
||||
/** Shared online roster used by CommandoCentrum (and linked from other ops hubs). */
|
||||
export async function fetchOpsOnlineUsers(
|
||||
limit = 40,
|
||||
): Promise<{ count: number; users: OnlineUser[] }> {
|
||||
const [countRow, onlineUsersRaw] = await Promise.all([
|
||||
db
|
||||
.select({ total: count() })
|
||||
.from(User)
|
||||
.where(eq(User.online, "1"))
|
||||
.then((rows) => rows[0]?.total ?? 0)
|
||||
.catch(() => 0),
|
||||
db
|
||||
.select({
|
||||
id: User.id,
|
||||
username: User.username,
|
||||
look: User.look,
|
||||
lastOnline: User.lastOnline,
|
||||
})
|
||||
.from(User)
|
||||
.where(eq(User.online, "1"))
|
||||
.orderBy(desc(User.lastOnline))
|
||||
.limit(limit)
|
||||
.then((rows) => rows)
|
||||
.catch(
|
||||
() =>
|
||||
[] as Array<{
|
||||
id: number;
|
||||
username: string;
|
||||
look: string;
|
||||
lastOnline: number;
|
||||
}>,
|
||||
),
|
||||
]);
|
||||
|
||||
function loadOnlineCount(): Promise<number> {
|
||||
return db
|
||||
.select({ total: count() })
|
||||
.from(User)
|
||||
.where(eq(User.online, "1"))
|
||||
.then((rows) => rows[0]?.total ?? 0);
|
||||
}
|
||||
|
||||
function loadOnlineRows(limit: number): Promise<OpsOnlineUserRow[]> {
|
||||
return db
|
||||
.select({
|
||||
id: User.id,
|
||||
username: User.username,
|
||||
look: User.look,
|
||||
lastOnline: User.lastOnline,
|
||||
})
|
||||
.from(User)
|
||||
.where(eq(User.online, "1"))
|
||||
.orderBy(desc(User.lastOnline))
|
||||
.limit(limit)
|
||||
.then((rows) => rows);
|
||||
}
|
||||
|
||||
function onlineRoster(countRow: number, onlineUsersRaw: OpsOnlineUserRow[]) {
|
||||
const users: OnlineUser[] = onlineUsersRaw.map((u) => ({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
@@ -46,25 +47,3 @@ function onlineRoster(countRow: number, onlineUsersRaw: OpsOnlineUserRow[]) {
|
||||
|
||||
return { count: countRow, users };
|
||||
}
|
||||
|
||||
/** Fail-aware roster for guarded System workflows. */
|
||||
export async function fetchOpsOnlineUsersStrict(
|
||||
limit = 40,
|
||||
): Promise<{ count: number; users: OnlineUser[] }> {
|
||||
const [countRow, onlineUsersRaw] = await Promise.all([
|
||||
loadOnlineCount(),
|
||||
loadOnlineRows(limit),
|
||||
]);
|
||||
return onlineRoster(countRow, onlineUsersRaw);
|
||||
}
|
||||
|
||||
/** Shared tolerant roster used by the legacy CommandoCentrum. */
|
||||
export async function fetchOpsOnlineUsers(
|
||||
limit = 40,
|
||||
): Promise<{ count: number; users: OnlineUser[] }> {
|
||||
const [countRow, onlineUsersRaw] = await Promise.all([
|
||||
loadOnlineCount().catch(() => 0),
|
||||
loadOnlineRows(limit).catch(() => []),
|
||||
]);
|
||||
return onlineRoster(countRow, onlineUsersRaw);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4,33 +4,23 @@ import { count, eq, ne } from "drizzle-orm";
|
||||
import { db, WebsiteHelpCenterTickets, WebsiteTicket } from "@/lib/db";
|
||||
|
||||
/** Open-queue sizes for dual ticket products (CMS desk vs help-center). */
|
||||
export async function fetchTicketQueueOpenCounts(options?: {
|
||||
readonly strict?: boolean;
|
||||
}): Promise<{
|
||||
export async function fetchTicketQueueOpenCounts(): Promise<{
|
||||
cmsOpen: number;
|
||||
helpOpen: number;
|
||||
}> {
|
||||
const cms = db
|
||||
.select({ total: count() })
|
||||
.from(WebsiteTicket)
|
||||
.where(ne(WebsiteTicket.status, "closed"));
|
||||
const help = db
|
||||
.select({ total: count() })
|
||||
.from(WebsiteHelpCenterTickets)
|
||||
.where(eq(WebsiteHelpCenterTickets.open, true));
|
||||
const readCount = async (
|
||||
query: typeof cms | typeof help,
|
||||
): Promise<number> => {
|
||||
const rows = await query;
|
||||
const value = Number(rows[0]?.total);
|
||||
if (!Number.isSafeInteger(value) || value < 0) {
|
||||
throw new Error("invalid ticket queue count");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const [cmsOpen, helpOpen] = await Promise.all([
|
||||
options?.strict ? readCount(cms) : readCount(cms).catch(() => 0),
|
||||
options?.strict ? readCount(help) : readCount(help).catch(() => 0),
|
||||
db
|
||||
.select({ total: count() })
|
||||
.from(WebsiteTicket)
|
||||
.where(ne(WebsiteTicket.status, "closed"))
|
||||
.then((rows) => rows[0]?.total ?? 0)
|
||||
.catch(() => 0),
|
||||
db
|
||||
.select({ total: count() })
|
||||
.from(WebsiteHelpCenterTickets)
|
||||
.where(eq(WebsiteHelpCenterTickets.open, true))
|
||||
.then((rows) => rows[0]?.total ?? 0)
|
||||
.catch(() => 0),
|
||||
]);
|
||||
return { cmsOpen, helpOpen };
|
||||
}
|
||||
Reference in new issue
Block a user