All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 18m47s
210 lines
6.1 KiB
TypeScript
210 lines
6.1 KiB
TypeScript
// 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.',
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|