fix(admin): polish tickets, photos purge note, drizzle contracts
Add queue banners/counts on ticket detail pages, document local-only photo purge, and harden Drizzle Kit smoke contracts after Prisma removal. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
ba82789166
commit
3bd712e744
18 files changed
+148
-78
No files matched your search
+1
-1
@@ -27,7 +27,7 @@ describe("positiveBigInt", () => {
|
||||
});
|
||||
|
||||
describe("pagination", () => {
|
||||
it("rejects fractional and malformed values before they reach Prisma", () => {
|
||||
it("rejects fractional and malformed values before they reach the DB layer", () => {
|
||||
const params = new URLSearchParams({ page: "2.5", perPage: "10.1" });
|
||||
|
||||
expect(pagination(params, 20, 100)).toEqual({
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
/**
|
||||
* JSON response helper for the public REST API. Serialises BigInt (Prisma ids)
|
||||
* JSON response helper for the public REST API. Serialises BigInt (DB ids)
|
||||
* to strings — JSON.stringify throws on BigInt otherwise — and sets CORS to
|
||||
* APP_URL so the game client / external integrations can read it.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export function isBanActive(
|
||||
return banExpire === 0 || banExpire > now;
|
||||
}
|
||||
|
||||
/** Prisma `where` fragment for currently active bans (incl. permanent). */
|
||||
/** Filter for currently active bans (incl. permanent). */
|
||||
export function activeBanWhere(now: number = unixNow()) {
|
||||
return {
|
||||
OR: [{ banExpire: 0 }, { banExpire: { gt: now } }],
|
||||
|
||||
@@ -2,7 +2,7 @@ import NextAuth from "next-auth";
|
||||
import { env } from "@/env";
|
||||
|
||||
// Proxy authentication must only decode the Auth.js session. Importing the
|
||||
// full CMS auth configuration here would also run Prisma/settings callbacks.
|
||||
// full CMS auth configuration here would also run DB/settings callbacks.
|
||||
export const { auth: proxyAuth } = NextAuth({
|
||||
trustHost: true,
|
||||
secret: env.AUTH_SECRET,
|
||||
|
||||
@@ -138,7 +138,7 @@ export async function loadCatalogItemsData(
|
||||
): Promise<CatalogItemsData> {
|
||||
// Load items via raw query to work around pageId Int vs VARCHAR mismatch
|
||||
const pageIdStr = String(pageId);
|
||||
// CAST: live Habbo DBs often store page_id as VARCHAR while Prisma maps Int.
|
||||
// CAST: live Habbo DBs often store page_id as VARCHAR while schema maps Int.
|
||||
const [rawItems] = (await db.execute(sql`
|
||||
SELECT * FROM catalog_items
|
||||
WHERE CAST(page_id AS CHAR) = ${pageIdStr}
|
||||
|
||||
@@ -12,7 +12,7 @@ function toInt(value: unknown, fallback = 0): number {
|
||||
|
||||
/**
|
||||
* Count catalog_items per page via raw SQL.
|
||||
* Real Habbo DBs often store page_id as VARCHAR; Prisma Int groupBy fails or returns 0.
|
||||
* Real Habbo DBs often store page_id as VARCHAR; numeric groupBy fails or returns 0.
|
||||
*/
|
||||
export async function getCatalogItemCounts(
|
||||
pageIds?: number[],
|
||||
@@ -216,7 +216,7 @@ export async function movePage(
|
||||
|
||||
/**
|
||||
* Delete catalog_items for the given page ids.
|
||||
* Habbo DBs often store page_id as VARCHAR; Prisma Int deleteMany misses rows.
|
||||
* Habbo DBs often store page_id as VARCHAR; typed Int deletes can miss rows.
|
||||
*/
|
||||
async function deleteCatalogItemsByPageIds(pageIds: number[]): Promise<void> {
|
||||
if (pageIds.length === 0) return;
|
||||
|
||||
@@ -55,10 +55,13 @@ describe("staff smoke contract", () => {
|
||||
expect(readFileSync("scripts/apply-migrations.ts", "utf8")).toContain(
|
||||
"drizzle/migrations",
|
||||
);
|
||||
expect(readFileSync("package.json", "utf8")).not.toContain('"prisma"');
|
||||
expect(readFileSync("package.json", "utf8")).not.toContain(
|
||||
'"@prisma/client"',
|
||||
);
|
||||
const pkg = readFileSync("package.json", "utf8");
|
||||
expect(pkg).not.toContain('"prisma"');
|
||||
expect(pkg).not.toContain('"@prisma/client"');
|
||||
expect(pkg).not.toContain("prisma:generate");
|
||||
expect(pkg).toContain('"db:generate": "drizzle-kit generate"');
|
||||
expect(pkg).toContain('"db:studio": "drizzle-kit studio"');
|
||||
expect(pkg).toContain('"db:introspect": "drizzle-kit introspect"');
|
||||
expect(readFileSync("drizzle.config.ts", "utf8")).toContain(
|
||||
"./drizzle/drafts",
|
||||
);
|
||||
@@ -144,9 +147,16 @@ describe("staff smoke contract", () => {
|
||||
"src/app/admin/help-tickets/page.tsx",
|
||||
"src/app/mod/tickets/desk/page.tsx",
|
||||
"src/app/mod/help-tickets/page.tsx",
|
||||
"src/app/admin/tickets/[id]/page.tsx",
|
||||
"src/app/admin/help-tickets/[id]/page.tsx",
|
||||
"src/app/mod/tickets/[id]/page.tsx",
|
||||
"src/app/mod/help-tickets/[id]/page.tsx",
|
||||
]) {
|
||||
expect(existsSync(path), path).toBe(true);
|
||||
expect(readFileSync(path, "utf8"), path).toContain("TicketQueueBanner");
|
||||
expect(readFileSync(path, "utf8"), path).toContain(
|
||||
"fetchTicketQueueOpenCounts",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -169,7 +179,16 @@ describe("staff smoke contract", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("ships items_base browser", () => {
|
||||
it("documents local-only photo file purge", () => {
|
||||
expect(readFileSync("src/app/admin/photos/page.tsx", "utf8")).toContain(
|
||||
"purgeHint",
|
||||
);
|
||||
expect(readFileSync("src/messages/en.json", "utf8")).toContain(
|
||||
"External CDN URLs are not purged",
|
||||
);
|
||||
});
|
||||
|
||||
it("ships items base admin CRUD", () => {
|
||||
expect(existsSync("src/app/admin/items/page.tsx")).toBe(true);
|
||||
expect(existsSync("src/app/admin/items/[id]/page.tsx")).toBe(true);
|
||||
expect(readFileSync("src/actions/items-base.ts", "utf8")).toContain(
|
||||
|
||||
Reference in new issue
Block a user