ADR-022 - websocket notificaitons - also more test fixes with stores
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 18m47s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 18m47s
This commit is contained in:
209
src/services/db/flyerLocation.db.ts
Normal file
209
src/services/db/flyerLocation.db.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
// src/services/db/flyerLocation.db.ts
|
||||
/**
|
||||
* Repository for managing flyer_locations (many-to-many relationship between flyers and store_locations).
|
||||
*/
|
||||
import type { Logger } from 'pino';
|
||||
import type { PoolClient, Pool } from 'pg';
|
||||
import { handleDbError } from './errors.db';
|
||||
import type { FlyerLocation } from '../../types';
|
||||
import { getPool } from './connection.db';
|
||||
|
||||
export class FlyerLocationRepository {
|
||||
private db: Pool | PoolClient;
|
||||
|
||||
constructor(dbClient?: PoolClient) {
|
||||
this.db = dbClient || getPool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a flyer to one or more store locations.
|
||||
* @param flyerId The ID of the flyer
|
||||
* @param storeLocationIds Array of store_location_ids to associate with this flyer
|
||||
* @param logger Logger instance
|
||||
* @returns Promise that resolves when all links are created
|
||||
*/
|
||||
async linkFlyerToLocations(
|
||||
flyerId: number,
|
||||
storeLocationIds: number[],
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (storeLocationIds.length === 0) {
|
||||
logger.warn({ flyerId }, 'No store locations provided for flyer linkage');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use VALUES with multiple rows for efficient bulk insert
|
||||
const values = storeLocationIds.map((_, index) => `($1, $${index + 2})`).join(', ');
|
||||
|
||||
const query = `
|
||||
INSERT INTO public.flyer_locations (flyer_id, store_location_id)
|
||||
VALUES ${values}
|
||||
ON CONFLICT (flyer_id, store_location_id) DO NOTHING
|
||||
`;
|
||||
|
||||
await this.db.query(query, [flyerId, ...storeLocationIds]);
|
||||
|
||||
logger.info(
|
||||
{ flyerId, locationCount: storeLocationIds.length },
|
||||
'Linked flyer to store locations',
|
||||
);
|
||||
} catch (error) {
|
||||
handleDbError(
|
||||
error,
|
||||
logger,
|
||||
'Database error in linkFlyerToLocations',
|
||||
{ flyerId, storeLocationIds },
|
||||
{
|
||||
defaultMessage: 'Failed to link flyer to store locations.',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a flyer to all locations of a given store.
|
||||
* This is a convenience method for the common case where a flyer is valid at all store locations.
|
||||
* @param flyerId The ID of the flyer
|
||||
* @param storeId The ID of the store
|
||||
* @param logger Logger instance
|
||||
* @returns Promise that resolves to the number of locations linked
|
||||
*/
|
||||
async linkFlyerToAllStoreLocations(
|
||||
flyerId: number,
|
||||
storeId: number,
|
||||
logger: Logger,
|
||||
): Promise<number> {
|
||||
try {
|
||||
const query = `
|
||||
INSERT INTO public.flyer_locations (flyer_id, store_location_id)
|
||||
SELECT $1, store_location_id
|
||||
FROM public.store_locations
|
||||
WHERE store_id = $2
|
||||
ON CONFLICT (flyer_id, store_location_id) DO NOTHING
|
||||
RETURNING store_location_id
|
||||
`;
|
||||
|
||||
const res = await this.db.query(query, [flyerId, storeId]);
|
||||
const linkedCount = res.rowCount || 0;
|
||||
|
||||
logger.info({ flyerId, storeId, linkedCount }, 'Linked flyer to all store locations');
|
||||
|
||||
return linkedCount;
|
||||
} catch (error) {
|
||||
handleDbError(
|
||||
error,
|
||||
logger,
|
||||
'Database error in linkFlyerToAllStoreLocations',
|
||||
{ flyerId, storeId },
|
||||
{
|
||||
defaultMessage: 'Failed to link flyer to all store locations.',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all location links for a flyer.
|
||||
* @param flyerId The ID of the flyer
|
||||
* @param logger Logger instance
|
||||
*/
|
||||
async unlinkAllLocations(flyerId: number, logger: Logger): Promise<void> {
|
||||
try {
|
||||
await this.db.query('DELETE FROM public.flyer_locations WHERE flyer_id = $1', [flyerId]);
|
||||
|
||||
logger.info({ flyerId }, 'Unlinked all locations from flyer');
|
||||
} catch (error) {
|
||||
handleDbError(
|
||||
error,
|
||||
logger,
|
||||
'Database error in unlinkAllLocations',
|
||||
{ flyerId },
|
||||
{
|
||||
defaultMessage: 'Failed to unlink locations from flyer.',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific location link from a flyer.
|
||||
* @param flyerId The ID of the flyer
|
||||
* @param storeLocationId The ID of the store location to unlink
|
||||
* @param logger Logger instance
|
||||
*/
|
||||
async unlinkLocation(flyerId: number, storeLocationId: number, logger: Logger): Promise<void> {
|
||||
try {
|
||||
await this.db.query(
|
||||
'DELETE FROM public.flyer_locations WHERE flyer_id = $1 AND store_location_id = $2',
|
||||
[flyerId, storeLocationId],
|
||||
);
|
||||
|
||||
logger.info({ flyerId, storeLocationId }, 'Unlinked location from flyer');
|
||||
} catch (error) {
|
||||
handleDbError(
|
||||
error,
|
||||
logger,
|
||||
'Database error in unlinkLocation',
|
||||
{ flyerId, storeLocationId },
|
||||
{
|
||||
defaultMessage: 'Failed to unlink location from flyer.',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all location IDs associated with a flyer.
|
||||
* @param flyerId The ID of the flyer
|
||||
* @param logger Logger instance
|
||||
* @returns Promise that resolves to an array of store_location_ids
|
||||
*/
|
||||
async getLocationIdsByFlyerId(flyerId: number, logger: Logger): Promise<number[]> {
|
||||
try {
|
||||
const res = await this.db.query<{ store_location_id: number }>(
|
||||
'SELECT store_location_id FROM public.flyer_locations WHERE flyer_id = $1',
|
||||
[flyerId],
|
||||
);
|
||||
|
||||
return res.rows.map((row) => row.store_location_id);
|
||||
} catch (error) {
|
||||
handleDbError(
|
||||
error,
|
||||
logger,
|
||||
'Database error in getLocationIdsByFlyerId',
|
||||
{ flyerId },
|
||||
{
|
||||
defaultMessage: 'Failed to get location IDs for flyer.',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all flyer_location records for a flyer.
|
||||
* @param flyerId The ID of the flyer
|
||||
* @param logger Logger instance
|
||||
* @returns Promise that resolves to an array of FlyerLocation objects
|
||||
*/
|
||||
async getFlyerLocationsByFlyerId(flyerId: number, logger: Logger): Promise<FlyerLocation[]> {
|
||||
try {
|
||||
const res = await this.db.query<FlyerLocation>(
|
||||
'SELECT * FROM public.flyer_locations WHERE flyer_id = $1',
|
||||
[flyerId],
|
||||
);
|
||||
|
||||
return res.rows;
|
||||
} catch (error) {
|
||||
handleDbError(
|
||||
error,
|
||||
logger,
|
||||
'Database error in getFlyerLocationsByFlyerId',
|
||||
{ flyerId },
|
||||
{
|
||||
defaultMessage: 'Failed to get flyer locations.',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user