fix(housekeeping): make rank synchronization recoverable
This commit is contained in:
1 parent
11ddf6cf0b
commit
daf272bad4
7 files changed
+804
-224
No files matched your search
@@ -43,15 +43,16 @@ export interface EmulatorRankForEdit extends EmulatorRankSummary {
|
||||
permissionMaxValues: Record<string, number>;
|
||||
}
|
||||
|
||||
/** Surface of `Db` used by these helpers (plain raw-SQL exec + transactions). */
|
||||
/** Surfaces used by rank helpers, including database transaction handles. */
|
||||
type SqlExecutor = Pick<Db, "execute">;
|
||||
type RawDb = Pick<Db, "execute" | "transaction">;
|
||||
|
||||
async function rawRows<T>(db: RawDb, query: SQL | string): Promise<T> {
|
||||
async function rawRows<T>(db: SqlExecutor, query: SQL | string): Promise<T> {
|
||||
const [rows] = await db.execute(query);
|
||||
return rows as T;
|
||||
}
|
||||
|
||||
async function rawExec(db: RawDb, query: SQL | string): Promise<number> {
|
||||
async function rawExec(db: SqlExecutor, query: SQL | string): Promise<number> {
|
||||
const [result] = await db.execute(query);
|
||||
return (result as ResultSetHeader).affectedRows;
|
||||
}
|
||||
@@ -195,6 +196,19 @@ export async function fetchEmulatorRankForEdit(
|
||||
export async function createEmulatorRank(
|
||||
db: RawDb,
|
||||
data: { rank_name: string; level: number },
|
||||
): Promise<number> {
|
||||
const id = await prepareEmulatorRankCreation(db);
|
||||
await db.transaction((tx) => createEmulatorRankInTransaction(tx, id, data));
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the dynamic permission column before opening the coupled DML
|
||||
* transaction. MySQL ALTER TABLE commits implicitly, so this schema-only step
|
||||
* is deliberately kept outside the atomic rank/ACL/audit unit.
|
||||
*/
|
||||
export async function prepareEmulatorRankCreation(
|
||||
db: SqlExecutor,
|
||||
): Promise<number> {
|
||||
const rows = await rawRows<{ next_id: number }[]>(
|
||||
db,
|
||||
@@ -203,38 +217,46 @@ export async function createEmulatorRank(
|
||||
const id = Number(rows[0]?.next_id ?? 1);
|
||||
await ensurePermissionRankColumn(db, id);
|
||||
await resetPermissionRankColumn(db, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await rawExec(
|
||||
tx,
|
||||
sql`
|
||||
export async function createEmulatorRankInTransaction(
|
||||
db: SqlExecutor,
|
||||
id: number,
|
||||
data: { rank_name: string; level: number },
|
||||
): Promise<void> {
|
||||
await rawExec(
|
||||
db,
|
||||
sql`
|
||||
INSERT INTO permission_ranks (id, rank_name, level)
|
||||
VALUES (${id}, ${data.rank_name}, ${data.level})
|
||||
`,
|
||||
);
|
||||
await rawExec(
|
||||
tx,
|
||||
sql`
|
||||
);
|
||||
await rawExec(
|
||||
db,
|
||||
sql`
|
||||
INSERT INTO permissions (id, rank_name, level)
|
||||
VALUES (${id}, ${data.rank_name}, ${data.level})
|
||||
ON DUPLICATE KEY UPDATE rank_name = VALUES(rank_name), level = VALUES(level)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
return id;
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteEmulatorRank(db: RawDb, rankId: number) {
|
||||
await db.transaction((tx) => deleteEmulatorRankInTransaction(tx, rankId));
|
||||
}
|
||||
|
||||
export async function deleteEmulatorRankInTransaction(
|
||||
db: SqlExecutor,
|
||||
rankId: number,
|
||||
): Promise<void> {
|
||||
await resetPermissionRankColumn(db, rankId);
|
||||
await db.transaction(async (tx) => {
|
||||
await rawExec(tx, sql`DELETE FROM permission_ranks WHERE id = ${rankId}`);
|
||||
await rawExec(tx, sql`DELETE FROM permissions WHERE id = ${rankId}`);
|
||||
});
|
||||
await rawExec(db, sql`DELETE FROM permission_ranks WHERE id = ${rankId}`);
|
||||
await rawExec(db, sql`DELETE FROM permissions WHERE id = ${rankId}`);
|
||||
}
|
||||
|
||||
export async function updateEmulatorRank(
|
||||
db: RawDb,
|
||||
db: SqlExecutor,
|
||||
rankId: number,
|
||||
fields: Record<string, string | number>,
|
||||
) {
|
||||
@@ -252,7 +274,7 @@ export async function updateEmulatorRank(
|
||||
}
|
||||
|
||||
async function updateTableFields(
|
||||
db: RawDb,
|
||||
db: SqlExecutor,
|
||||
table: string,
|
||||
rankId: number,
|
||||
fields: Record<string, string | number>,
|
||||
@@ -271,7 +293,7 @@ async function updateTableFields(
|
||||
}
|
||||
|
||||
async function updatePermissionDefinitionValues(
|
||||
db: RawDb,
|
||||
db: SqlExecutor,
|
||||
rankId: number,
|
||||
permissionFields: Record<string, number>,
|
||||
) {
|
||||
@@ -297,7 +319,7 @@ async function updatePermissionDefinitionValues(
|
||||
}
|
||||
|
||||
async function updateLegacyPermissionFields(
|
||||
db: RawDb,
|
||||
db: SqlExecutor,
|
||||
rankId: number,
|
||||
fields: Record<string, string | number>,
|
||||
) {
|
||||
@@ -309,7 +331,7 @@ async function updateLegacyPermissionFields(
|
||||
return updateTableFields(db, "permissions", rankId, existingFields);
|
||||
}
|
||||
|
||||
async function ensurePermissionRankColumn(db: RawDb, rankId: number) {
|
||||
async function ensurePermissionRankColumn(db: SqlExecutor, rankId: number) {
|
||||
const rankColumn = getRankPermissionColumn(rankId);
|
||||
const exists = await rawRows<{ c: number }[]>(
|
||||
db,
|
||||
@@ -332,7 +354,7 @@ async function ensurePermissionRankColumn(db: RawDb, rankId: number) {
|
||||
);
|
||||
}
|
||||
|
||||
async function resetPermissionRankColumn(db: RawDb, rankId: number) {
|
||||
async function resetPermissionRankColumn(db: SqlExecutor, rankId: number) {
|
||||
const rankColumn = getRankPermissionColumn(rankId);
|
||||
await rawExec(
|
||||
db,
|
||||
@@ -343,7 +365,7 @@ async function resetPermissionRankColumn(db: RawDb, rankId: number) {
|
||||
);
|
||||
}
|
||||
|
||||
async function getTableColumns(db: RawDb, table: string) {
|
||||
async function getTableColumns(db: SqlExecutor, table: string) {
|
||||
const rows = await rawRows<{ column_name: string }[]>(
|
||||
db,
|
||||
sql`
|
||||
|
||||
Reference in new issue
Block a user