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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 34x 2x 2x 2x 2x 2x 2x 2x 2x 4x 4x 2x 2x 2x 3x 2x 1x 3x 2x 13x 13x 13x 13x 2x 2x 13x 1x 1x 48x 13x 48x 47x 13x 13x 10x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x | // src/services/db/address.db.ts
import type { Pool, PoolClient } from 'pg';
import { getPool } from './connection.db';
import type { Logger } from 'pino';
import { NotFoundError, handleDbError } from './errors.db';
import { Address } from '../../types';
export class AddressRepository {
// The repository only needs an object with a `query` method, matching the Pool/PoolClient interface.
// Using `Pick` makes this dependency explicit and simplifies testing by reducing the mock surface.
private db: Pick<Pool | PoolClient, 'query'>;
constructor(db: Pick<Pool | PoolClient, 'query'> = getPool()) {
this.db = db;
}
/**
* Retrieves a single address by its ID.
* @param addressId The ID of the address to retrieve.
* @returns A promise that resolves to the Address object or undefined.
*/
async getAddressById(addressId: number, logger: Logger): Promise<Address> {
try {
const res = await this.db.query<Address>(
'SELECT * FROM public.addresses WHERE address_id = $1',
[addressId],
);
if (res.rowCount === 0) {
throw new NotFoundError(`Address with ID ${addressId} not found.`);
}
return res.rows[0];
} catch (error) {
handleDbError(
error,
logger,
'Database error in getAddressById',
{ addressId },
{
defaultMessage: 'Failed to retrieve address.',
},
);
}
}
/**
* Creates or updates an address and returns its ID.
* This function uses an "upsert" pattern.
* @param address The address data.
* @returns The ID of the created or updated address.
*/
async upsertAddress(address: Partial<Address>, logger: Logger): Promise<number> {
try {
const { address_id, ...addressData } = address;
const columns = Object.keys(addressData);
const values = Object.values(addressData);
// If an address_id is provided, include it for the ON CONFLICT clause.
if (address_id) {
columns.unshift('address_id');
values.unshift(address_id);
}
// Dynamically build the parameter placeholders ($1, $2, etc.)
const valuePlaceholders = columns.map((_, i) => `$${i + 1}`).join(', ');
// Dynamically build the SET clause for the UPDATE part.
// EXCLUDED refers to the values from the failed INSERT attempt.
const updateSetClauses = columns
.filter((col) => col !== 'address_id') // Don't update the primary key
.map((col) => `${col} = EXCLUDED.${col}`)
.join(', ');
const query = `
INSERT INTO public.addresses (${columns.join(', ')})
VALUES (${valuePlaceholders})
ON CONFLICT (address_id) DO UPDATE
SET ${updateSetClauses},
updated_at = now()
RETURNING address_id;
`;
const res = await this.db.query<{ address_id: number }>(query, values);
return res.rows[0].address_id;
} catch (error) {
handleDbError(
error,
logger,
'Database error in upsertAddress',
{ address },
{
uniqueMessage: 'An identical address already exists.',
defaultMessage: 'Failed to upsert address.',
},
);
}
}
/**
* Searches for addresses by text (matches against address_line_1, city, or postal_code).
* @param query Search query
* @param logger Logger instance
* @param limit Maximum number of results (default: 10)
* @returns Array of matching Address objects
*/
async searchAddressesByText(
query: string,
logger: Logger,
limit: number = 10,
): Promise<Address[]> {
try {
const sql = `
SELECT * FROM public.addresses
WHERE
address_line_1 ILIKE $1 OR
city ILIKE $1 OR
postal_code ILIKE $1
ORDER BY city ASC, address_line_1 ASC
LIMIT $2
`;
const result = await this.db.query<Address>(sql, [`%${query}%`, limit]);
return result.rows;
} catch (error) {
handleDbError(
error,
logger,
'Database error in searchAddressesByText',
{ query, limit },
{
defaultMessage: 'Failed to search addresses.',
},
);
}
}
/**
* Retrieves all addresses associated with a given store.
* @param storeId The store ID
* @param logger Logger instance
* @returns Array of Address objects
*/
async getAddressesByStoreId(storeId: number, logger: Logger): Promise<Address[]> {
try {
const query = `
SELECT a.*
FROM public.addresses a
INNER JOIN public.store_locations sl ON a.address_id = sl.address_id
WHERE sl.store_id = $1
ORDER BY sl.created_at ASC
`;
const result = await this.db.query<Address>(query, [storeId]);
return result.rows;
} catch (error) {
handleDbError(
error,
logger,
'Database error in getAddressesByStoreId',
{ storeId },
{
defaultMessage: 'Failed to retrieve addresses for store.',
},
);
}
}
}
|