ADR1-3 on routes + db files
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 17m30s

This commit is contained in:
2025-12-12 16:09:59 -08:00
parent e37a32c890
commit 117f034b2b
32 changed files with 934 additions and 812 deletions

View File

@@ -1,6 +1,6 @@
// src/services/db/admin.db.ts
import type { Pool, PoolClient } from 'pg';
import { getPool } from './connection.db';
import { getPool, withTransaction } from './connection.db';
import { ForeignKeyConstraintError, NotFoundError } from './errors.db';
import { logger } from '../logger.server';
import { SuggestedCorrection, MostFrequentSaleItem, Recipe, RecipeComment, UnmatchedFlyerItem, ActivityLogItem, Receipt, User, AdminUserView } from '../../types';
@@ -77,13 +77,11 @@ export class AdminRepository {
[correctionId]
);
if (res.rowCount === 0) {
// This could happen if the correction was already processed or doesn't exist.
logger.warn(`Attempted to reject correction ID ${correctionId}, but it was not found or not in 'pending' state.`);
// We don't throw an error here, as the end state (not pending) is achieved.
} else {
logger.info(`Successfully rejected correction ID: ${correctionId}`);
throw new NotFoundError(`Correction with ID ${correctionId} not found or not in 'pending' state.`);
}
logger.info(`Successfully rejected correction ID: ${correctionId}`);
} catch (error) {
if (error instanceof NotFoundError) throw error;
logger.error('Database error in rejectCorrection:', { error, correctionId });
throw new Error('Failed to reject correction.');
}
@@ -308,16 +306,14 @@ export class AdminRepository {
'UPDATE public.recipes SET status = $1 WHERE recipe_id = $2 RETURNING *',
[status, recipeId]
);
if (res.rowCount === 0) {
throw new NotFoundError(`Recipe with ID ${recipeId} not found.`);
}
if (res.rowCount === 0) throw new NotFoundError(`Recipe with ID ${recipeId} not found.`);
return res.rows[0];
} catch (error) {
if (error instanceof NotFoundError) {
throw error;
}
logger.error('Database error in updateRecipeStatus:', { error, recipeId, status });
throw new Error('Failed to update recipe status.');
throw new Error('Failed to update recipe status.'); // Keep generic for other DB errors
}
}
@@ -328,35 +324,31 @@ export class AdminRepository {
* @param masterItemId The ID of the `master_grocery_items` to link to.
*/
async resolveUnmatchedFlyerItem(unmatchedFlyerItemId: number, masterItemId: number): Promise<void> {
const client = await getPool().connect();
try {
await client.query('BEGIN');
await withTransaction(async (client) => {
// First, get the flyer_item_id from the unmatched record
const unmatchedRes = await client.query<{
flyer_item_id: number;
}>('SELECT flyer_item_id FROM public.unmatched_flyer_items WHERE unmatched_flyer_item_id = $1 FOR UPDATE', [
unmatchedFlyerItemId,
]);
// First, get the flyer_item_id from the unmatched record
const unmatchedRes = await client.query<{ flyer_item_id: number }>(
'SELECT flyer_item_id FROM public.unmatched_flyer_items WHERE unmatched_flyer_item_id = $1 FOR UPDATE',
[unmatchedFlyerItemId]
);
if (unmatchedRes.rowCount === 0) {
throw new NotFoundError(`Unmatched flyer item with ID ${unmatchedFlyerItemId} not found.`);
}
const { flyer_item_id } = unmatchedRes.rows[0];
if (unmatchedRes.rowCount === 0) {
throw new NotFoundError(`Unmatched flyer item with ID ${unmatchedFlyerItemId} not found.`);
}
const { flyer_item_id } = unmatchedRes.rows[0];
// Next, update the original flyer_items table with the correct master_item_id
await client.query('UPDATE public.flyer_items SET master_item_id = $1 WHERE flyer_item_id = $2', [masterItemId, flyer_item_id]);
// Next, update the original flyer_items table with the correct master_item_id
await client.query('UPDATE public.flyer_items SET master_item_id = $1 WHERE flyer_item_id = $2', [masterItemId, flyer_item_id]);
// Finally, update the status of the unmatched record to 'resolved'
await client.query("UPDATE public.unmatched_flyer_items SET status = 'resolved' WHERE unmatched_flyer_item_id = $1", [unmatchedFlyerItemId]);
// Finally, update the status of the unmatched record to 'resolved'
await client.query("UPDATE public.unmatched_flyer_items SET status = 'resolved' WHERE unmatched_flyer_item_id = $1", [unmatchedFlyerItemId]);
await client.query('COMMIT');
logger.info(`Successfully resolved unmatched item ${unmatchedFlyerItemId} to master item ${masterItemId}.`);
logger.info(`Successfully resolved unmatched item ${unmatchedFlyerItemId} to master item ${masterItemId}.`);
});
} catch (error) {
await client.query('ROLLBACK');
logger.error('Database transaction error in resolveUnmatchedFlyerItem:', { error, unmatchedFlyerItemId, masterItemId });
throw new Error('Failed to resolve unmatched flyer item.');
} finally {
client.release();
}
}
@@ -366,8 +358,12 @@ export class AdminRepository {
*/
async ignoreUnmatchedFlyerItem(unmatchedFlyerItemId: number): Promise<void> {
try {
await this.db.query("UPDATE public.unmatched_flyer_items SET status = 'ignored' WHERE unmatched_flyer_item_id = $1", [unmatchedFlyerItemId]);
const res = await this.db.query("UPDATE public.unmatched_flyer_items SET status = 'ignored' WHERE unmatched_flyer_item_id = $1 AND status = 'pending'", [unmatchedFlyerItemId]);
if (res.rowCount === 0) {
throw new NotFoundError(`Unmatched flyer item with ID ${unmatchedFlyerItemId} not found or not in 'pending' state.`);
}
} catch (error) {
if (error instanceof NotFoundError) throw error;
logger.error('Database error in ignoreUnmatchedFlyerItem:', { error, unmatchedFlyerItemId });
throw new Error('Failed to ignore unmatched flyer item.');
}
@@ -465,11 +461,15 @@ export class AdminRepository {
// prettier-ignore
async updateBrandLogo(brandId: number, logoUrl: string): Promise<void> {
try {
await this.db.query(
const res = await this.db.query(
'UPDATE public.brands SET logo_url = $1 WHERE brand_id = $2',
[logoUrl, brandId]
);
if (res.rowCount === 0) {
throw new NotFoundError(`Brand with ID ${brandId} not found.`);
}
} catch (error) {
if (error instanceof NotFoundError) throw error;
logger.error('Database error in updateBrandLogo:', { error, brandId });
throw new Error('Failed to update brand logo in database.');
}
@@ -487,11 +487,10 @@ export class AdminRepository {
`UPDATE public.receipts SET status = $1, processed_at = CASE WHEN $1 IN ('completed', 'failed') THEN now() ELSE processed_at END WHERE receipt_id = $2 RETURNING *`,
[status, receiptId]
);
if (res.rowCount === 0) {
throw new NotFoundError(`Receipt with ID ${receiptId} not found.`);
}
if (res.rowCount === 0) throw new NotFoundError(`Receipt with ID ${receiptId} not found.`);
return res.rows[0];
} catch (error) {
if (error instanceof NotFoundError) throw error;
logger.error('Database error in updateReceiptStatus:', { error, receiptId, status });
throw new Error('Failed to update receipt status.');
}