Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 59x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 4x 2x 3x 2x 2x 2x 2x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 2x 2x 2x 3x 2x 2x 2x 3x 2x 2x | // src/services/flyerPersistenceService.server.ts
import type { Logger } from 'pino';
import type { PoolClient } from 'pg';
import { withTransaction as defaultWithTransaction } from './db/connection.db';
import { createFlyerAndItems } from './db/flyer.db';
import { AdminRepository } from './db/admin.db';
import { GamificationRepository } from './db/gamification.db';
import { cacheService } from './cacheService.server';
import type { FlyerInsert, FlyerItemInsert, Flyer } from '../types';
export type WithTransactionFn = <T>(callback: (client: PoolClient) => Promise<T>) => Promise<T>;
export class FlyerPersistenceService {
private withTransaction: WithTransactionFn;
constructor(withTransactionFn: WithTransactionFn = defaultWithTransaction) {
this.withTransaction = withTransactionFn;
}
/**
* Allows replacing the withTransaction function at runtime.
* This is primarily used for testing to inject mock implementations.
* Pass null to reset to the default implementation.
* @internal
*/
_setWithTransaction(fn: WithTransactionFn | null): void {
this.withTransaction = fn ?? defaultWithTransaction;
}
/**
* Saves the flyer and its items to the database within a transaction.
* Also logs the activity and invalidates related cache entries.
*/
async saveFlyer(
flyerData: FlyerInsert,
itemsForDb: FlyerItemInsert[],
userId: string | undefined,
logger: Logger,
): Promise<Flyer> {
const flyer = await this.withTransaction(async (client) => {
const { flyer, items } = await createFlyerAndItems(flyerData, itemsForDb, logger, client);
logger.info(
`Successfully processed flyer: ${flyer.file_name} (ID: ${flyer.flyer_id}) with ${items.length} items.`,
);
// Log activity if a user uploaded it
if (userId) {
const transactionalAdminRepo = new AdminRepository(client);
await transactionalAdminRepo.logActivity(
{
userId: userId,
action: 'flyer_processed',
displayText: `Processed a new flyer for ${flyerData.store_name}.`,
details: { flyerId: flyer.flyer_id, storeName: flyerData.store_name },
},
logger,
);
// Award 'First-Upload' achievement
const gamificationRepo = new GamificationRepository(client);
await gamificationRepo.awardAchievement(userId, 'First-Upload', logger);
}
return flyer;
});
// Invalidate flyer list cache after successful creation (fire-and-forget)
cacheService.invalidateFlyers(logger).catch(() => {
// Error already logged in invalidateFlyers
});
return flyer;
}
}
|