refactor(db): typed query helpers, shared test FormData helper
Replace raw db.execute tuple casts with queryRows/rowsFrom/execResult/ affectedRows helpers from lib/db, drop redundant mysql2 casts on typed query builders, and centralize per-test fakeForm into test/fake-form. Update db mocks in tests so helpers resolve against mocked execute.
This commit is contained in:
1 parent
2e25b39364
commit
8638e81444
116 files changed
+866
-897
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "@/lib/db";
|
||||
import { db, queryRows, rowsFrom } from "@/lib/db";
|
||||
import {
|
||||
type CatalogOfferRebuildResult,
|
||||
type FurniDataReconcileResult,
|
||||
@@ -56,11 +56,8 @@ const STR_COLS: Array<[string, string]> = [
|
||||
["catalog_version_offers", "item_ids"],
|
||||
];
|
||||
|
||||
type Rows<T> = [T[], unknown];
|
||||
|
||||
async function query<T>(q: unknown): Promise<T[]> {
|
||||
const [rows] = (await db.execute(q as never)) as unknown as Rows<T>;
|
||||
return rows ?? [];
|
||||
return queryRows<T>(q as never);
|
||||
}
|
||||
|
||||
async function exists(table: string): Promise<boolean> {
|
||||
@@ -228,37 +225,33 @@ export async function countEmulatorIssues(): Promise<number> {
|
||||
}
|
||||
|
||||
export async function listEmulatorIssues(): Promise<EmulatorCatalogIssue[]> {
|
||||
const [items] = (await db.execute(sql`
|
||||
const items = await queryRows<{
|
||||
id: number;
|
||||
pageId: number | string;
|
||||
itemIds: string;
|
||||
costCredits: number;
|
||||
costPoints: number;
|
||||
pointsType: number;
|
||||
amount: number;
|
||||
limitedStack: number;
|
||||
limitedSells: number;
|
||||
orderNumber: number;
|
||||
}>(sql`
|
||||
SELECT
|
||||
ci.id, ci.page_id AS pageId, ci.item_ids AS itemIds,
|
||||
ci.cost_credits AS costCredits, ci.cost_points AS costPoints,
|
||||
ci.points_type AS pointsType, ci.amount, ci.limited_stack AS limitedStack,
|
||||
ci.limited_sells AS limitedSells, ci.order_number AS orderNumber
|
||||
FROM catalog_items ci
|
||||
`)) as unknown as [
|
||||
Array<{
|
||||
id: number;
|
||||
pageId: number | string;
|
||||
itemIds: string;
|
||||
costCredits: number;
|
||||
costPoints: number;
|
||||
pointsType: number;
|
||||
amount: number;
|
||||
limitedStack: number;
|
||||
limitedSells: number;
|
||||
orderNumber: number;
|
||||
}>,
|
||||
unknown,
|
||||
];
|
||||
const [pages] = (await db.execute(
|
||||
`);
|
||||
const pages = await queryRows<{
|
||||
id: number;
|
||||
parentId: number | string;
|
||||
orderNum: number;
|
||||
}>(
|
||||
sql`SELECT id, parent_id AS parentId, order_num AS orderNum FROM catalog_pages`,
|
||||
)) as unknown as [
|
||||
Array<{ id: number; parentId: number | string; orderNum: number }>,
|
||||
unknown,
|
||||
];
|
||||
const [bases] = (await db.execute(
|
||||
sql`SELECT id FROM items_base`,
|
||||
)) as unknown as [Array<{ id: number }>, unknown];
|
||||
);
|
||||
const bases = await queryRows<{ id: number }>(sql`SELECT id FROM items_base`);
|
||||
|
||||
const pageIds = new Set(pages.map((r) => Number(r.id)));
|
||||
const baseIds = new Set(bases.map((r) => Number(r.id)));
|
||||
@@ -504,13 +497,16 @@ export async function fixEmulatorIssues(): Promise<FixEmulatorResult> {
|
||||
// duplicate sibling order -> re-number sequentially per page
|
||||
const dupOrders = issues.filter((i) => i.type === "duplicate_order");
|
||||
if (dupOrders.length > 0) {
|
||||
const [allRows] = (await tx.execute(sql`
|
||||
const allRows = rowsFrom<{
|
||||
id: number;
|
||||
pageId: number | string;
|
||||
orderNumber: number;
|
||||
}>(
|
||||
await tx.execute(sql`
|
||||
SELECT id, page_id AS pageId, order_number AS orderNumber
|
||||
FROM catalog_items ORDER BY page_id, order_number, id
|
||||
`)) as unknown as [
|
||||
Array<{ id: number; pageId: number | string; orderNumber: number }>,
|
||||
unknown,
|
||||
];
|
||||
`),
|
||||
);
|
||||
let lastPage: number | null = null;
|
||||
let next = 1;
|
||||
for (const row of allRows) {
|
||||
@@ -535,12 +531,14 @@ export async function fixEmulatorIssues(): Promise<FixEmulatorResult> {
|
||||
const cyclePages = issues.filter((i) => i.type === "page_cycle");
|
||||
if (cyclePages.length > 0) {
|
||||
const cycleIds = cyclePages.map((i) => i.id);
|
||||
const [cycleRows] = (await tx.execute(sql`
|
||||
const cycleRows = rowsFrom<{
|
||||
id: number | string;
|
||||
parentId: number | string;
|
||||
}>(
|
||||
await tx.execute(sql`
|
||||
SELECT id, parent_id AS parentId FROM catalog_pages WHERE id IN (${sql.join(cycleIds, sql`, `)})
|
||||
`)) as unknown as [
|
||||
Array<{ id: number | string; parentId: number | string }>,
|
||||
unknown,
|
||||
];
|
||||
`),
|
||||
);
|
||||
const parentOf = new Map(
|
||||
cycleRows.map((r) => [Number(r.id), Number(r.parentId)]),
|
||||
);
|
||||
@@ -574,13 +572,15 @@ export async function fixEmulatorIssues(): Promise<FixEmulatorResult> {
|
||||
if (pageOrderIssues.length > 0) {
|
||||
const parents = [...new Set(pageOrderIssues.map((i) => i.pageId))];
|
||||
for (const parent of parents) {
|
||||
const [children] = (await tx.execute(sql`
|
||||
const children = rowsFrom<{
|
||||
id: number | string;
|
||||
orderNum: number;
|
||||
}>(
|
||||
await tx.execute(sql`
|
||||
SELECT id, order_num AS orderNum FROM catalog_pages
|
||||
WHERE parent_id = ${String(parent)} ORDER BY order_num, id
|
||||
`)) as unknown as [
|
||||
Array<{ id: number | string; orderNum: number }>,
|
||||
unknown,
|
||||
];
|
||||
`),
|
||||
);
|
||||
let next = 1;
|
||||
for (const child of children) {
|
||||
const newOrder = next++;
|
||||
@@ -659,19 +659,19 @@ export async function dedupeCatalogPages(): Promise<CatalogPageDedupResult> {
|
||||
if (pages.length < 2) continue;
|
||||
const [winner, ...losers] = pages.map((p) => p.id);
|
||||
for (const loser of losers) {
|
||||
const ci = (await tx.execute(sql`
|
||||
UPDATE catalog_items SET page_id = ${winner} WHERE page_id = ${loser}`)) as unknown as [
|
||||
Record<string, unknown>,
|
||||
unknown,
|
||||
];
|
||||
remapped += Number(ci[0]?.affectedRows ?? 0);
|
||||
const affected =
|
||||
rowsFrom<{ affectedRows: number }>(
|
||||
await tx.execute(sql`
|
||||
UPDATE catalog_items SET page_id = ${winner} WHERE page_id = ${loser}`),
|
||||
)[0]?.affectedRows ?? 0;
|
||||
remapped += Number(affected);
|
||||
if (await exists("catalog_items_bc")) {
|
||||
const bci = (await tx.execute(sql`
|
||||
UPDATE catalog_items_bc SET page_id = ${winner} WHERE page_id = ${loser}`)) as unknown as [
|
||||
Record<string, unknown>,
|
||||
unknown,
|
||||
];
|
||||
remapped += Number(bci[0]?.affectedRows ?? 0);
|
||||
const bciAffected =
|
||||
rowsFrom<{ affectedRows: number }>(
|
||||
await tx.execute(sql`
|
||||
UPDATE catalog_items_bc SET page_id = ${winner} WHERE page_id = ${loser}`),
|
||||
)[0]?.affectedRows ?? 0;
|
||||
remapped += Number(bciAffected);
|
||||
}
|
||||
// Keep any child pages by reparenting them onto the survivor.
|
||||
await tx.execute(sql`
|
||||
@@ -738,10 +738,12 @@ export async function removeDuplicateItemsBase(): Promise<DedupResult> {
|
||||
const ids = g.ids.filter((x) => x !== g.keeper);
|
||||
for (const dupId of ids) {
|
||||
for (const [table, col] of activeInt) {
|
||||
const r = (await tx.execute(
|
||||
sql`UPDATE ${sql.raw(table)} t SET ${sql.raw(col)} = ${g.keeper} WHERE ${sql.raw(col)} = ${dupId}`,
|
||||
)) as unknown as [Record<string, unknown>, unknown];
|
||||
remapped += Number(r[0]?.affectedRows ?? 0);
|
||||
const r = rowsFrom<{ affectedRows: number }>(
|
||||
await tx.execute(
|
||||
sql`UPDATE ${sql.raw(table)} t SET ${sql.raw(col)} = ${g.keeper} WHERE ${sql.raw(col)} = ${dupId}`,
|
||||
),
|
||||
)[0];
|
||||
remapped += Number(r?.affectedRows ?? 0);
|
||||
}
|
||||
for (const [table, col] of activeStr) {
|
||||
const from = String(dupId);
|
||||
@@ -783,12 +785,12 @@ async function dedupCatalogTable(table: string): Promise<number> {
|
||||
? ["page_id", "item_ids", "catalog_name", "extradata"]
|
||||
: ["page_id", "item_ids"];
|
||||
const on = grp.map((c) => `t.${c} <=> d.${c}`).join(" AND ");
|
||||
const r = (await db.execute(
|
||||
const r = await queryRows<{ affectedRows: number }>(
|
||||
sql.raw(
|
||||
`DELETE t FROM ${table} t JOIN (SELECT ${grp.join(",")}, MIN(id) AS _keep FROM ${table} GROUP BY ${grp.join(",")} HAVING COUNT(*) > 1) d ON ${on} WHERE t.id <> d._keep`,
|
||||
),
|
||||
)) as unknown as [Record<string, unknown>, unknown];
|
||||
return Number(r[0]?.affectedRows ?? 0);
|
||||
);
|
||||
return r[0]?.affectedRows ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user