a fun bot for the hc slack

feat: fix user total drift

dunkirk.sh 00bbd6ba 1425e9b2

verified
Changed files
+110
src
features
takes
libs
+22
src/features/takes/index.ts
···
import setupCommands from "./setup/commands";
import setupActions from "./setup/actions";
+
import { validateAndFixUserTotals } from "../../libs/userTotals";
+
import * as Sentry from "@sentry/bun";
const takes = async () => {
setupCommands();
setupActions();
+
+
// Validate and fix user totals on startup
+
try {
+
const result = await validateAndFixUserTotals();
+
if (result.fixed > 0) {
+
console.log(`Fixed ${result.fixed} users with total takes time drift`);
+
}
+
if (result.errors.length > 0) {
+
console.error(`Failed to fix ${result.errors.length} users`);
+
Sentry.captureMessage("Failed to fix some user totals", {
+
level: "warning",
+
extra: { errors: result.errors }
+
});
+
}
+
} catch (error) {
+
console.error("Error while validating user totals:", error);
+
Sentry.captureException(error, {
+
tags: { type: "user_totals_validation" }
+
});
+
}
};
export default takes;
+88
src/libs/userTotals.ts
···
+
import { db } from "./db";
+
import { users as usersTable, takes as takesTable } from "./schema";
+
import { sql, eq, and, ne } from "drizzle-orm";
+
+
/**
+
* Finds and corrects any drift between the computed user total time and the stored value.
+
* This helps ensure time calculations remain accurate even if triggers fail or data gets out of sync.
+
*/
+
export async function validateAndFixUserTotals() {
+
try {
+
console.log("Validating user totals...");
+
+
// First, get the calculated totals per user
+
const calculatedTotals = await db
+
.select({
+
userId: takesTable.userId,
+
calculatedTotal:
+
sql<number>`COALESCE(SUM(${takesTable.elapsedTime}), 0)::integer`.as(
+
"calculated_total",
+
),
+
})
+
.from(takesTable)
+
.groupBy(takesTable.userId);
+
+
// Convert to a map for easier lookup
+
const totalsMap = new Map(
+
calculatedTotals.map((item) => [item.userId, item.calculatedTotal]),
+
);
+
+
// Get all users
+
const allUsers = await db
+
.select({
+
id: usersTable.id,
+
storedTotal: usersTable.totalTakesTime,
+
})
+
.from(usersTable);
+
+
// Find users with drift
+
const driftedUsers = allUsers
+
.filter((user) => {
+
const calculatedTotal = totalsMap.get(user.id) || 0;
+
return user.storedTotal !== calculatedTotal;
+
})
+
.map((user) => ({
+
id: user.id,
+
storedTotal: user.storedTotal,
+
calculatedTotal: totalsMap.get(user.id) || 0,
+
}));
+
+
if (driftedUsers.length === 0) {
+
console.log("✅ All user totals are in sync");
+
return { fixed: 0, errors: [] };
+
}
+
+
console.log(
+
`❌ Found ${driftedUsers.length} users with incorrect totals`,
+
);
+
+
// Fix each drifted user
+
const errors: string[] = [];
+
let fixed = 0;
+
+
for (const user of driftedUsers) {
+
try {
+
await db
+
.update(usersTable)
+
.set({ totalTakesTime: user.calculatedTotal })
+
.where(eq(usersTable.id, user.id));
+
+
console.log(
+
`Fixed user ${user.id}: ${user.storedTotal} → ${user.calculatedTotal}`,
+
);
+
fixed++;
+
} catch (error) {
+
const errorMsg = `Failed to fix user ${user.id}: ${error}`;
+
console.error(errorMsg);
+
errors.push(errorMsg);
+
}
+
}
+
+
console.log(`✅ Fixed ${fixed} user totals`);
+
+
return { fixed, errors };
+
} catch (error) {
+
console.error("Error validating user totals:", error);
+
throw error;
+
}
+
}