many fixes resulting from latest refactoring
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 5m55s

This commit is contained in:
2025-12-09 13:01:49 -08:00
parent 365bd7cce0
commit dbdee8e456
6 changed files with 95 additions and 64 deletions

View File

@@ -58,12 +58,6 @@ router.post('/register', async (req, res, next) => {
return res.status(400).json({ message: `Password is too weak. ${feedback || 'Please choose a stronger password.'}`.trim() });
}
const existingUser = await userRepo.findUserByEmail(email);
if (existingUser) {
logger.warn(`Registration attempt for existing email: ${email}`);
return res.status(409).json({ message: 'User with that email already exists.' });
}
const client = await getPool().connect();
let newUser;
try {
@@ -73,7 +67,14 @@ router.post('/register', async (req, res, next) => {
logger.info(`Hashing password for new user: ${email}`);
const repoWithTransaction = new (await import('../services/db/user.db')).UserRepository(client);
newUser = await repoWithTransaction.createUser(email, hashedPassword, { full_name, avatar_url });
try {
newUser = await repoWithTransaction.createUser(email, hashedPassword, { full_name, avatar_url });
} catch (error) {
if (error instanceof UniqueConstraintError) {
return res.status(409).json({ message: error.message });
}
throw error;
}
await client.query('COMMIT');
} catch (error: unknown) {

View File

@@ -53,7 +53,8 @@ export class BudgetRepository {
return res.rows[0];
} catch (error) {
await client.query('ROLLBACK');
if (error instanceof Error && 'code' in error && error.code === '23503') {
// The patch requested this specific error handling.
if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('The specified user does not exist.');
}
logger.error('Database error in createBudget:', { error });
@@ -80,14 +81,17 @@ export class BudgetRepository {
period = COALESCE($3, period),
start_date = COALESCE($4, start_date)
WHERE budget_id = $5 AND user_id = $6 RETURNING *`,
[name, amount_cents, period, start_date, budgetId, userId]
[name, amount_cents, period, start_date, budgetId, userId],
);
if (res.rowCount === 0) {
throw new Error('Budget not found or user does not have permission to update.');
}
return res.rows[0];
} catch (error) {
if (error instanceof Error && error.message.startsWith('Budget not found')) throw error;
if ((error as Error).message.includes('Budget not found')) {
throw error;
}
logger.error('Database error in updateBudget:', { error, budgetId, userId });
throw new Error('Failed to update budget.');
}
@@ -100,12 +104,14 @@ export class BudgetRepository {
*/
async deleteBudget(budgetId: number, userId: string): Promise<void> {
try {
const res = await this.db.query('DELETE FROM public.budgets WHERE budget_id = $1 AND user_id = $2', [budgetId, userId]);
if (res.rowCount === 0) {
const result = await this.db.query('DELETE FROM public.budgets WHERE budget_id = $1 AND user_id = $2', [budgetId, userId]);
if (result.rowCount === 0) {
throw new Error('Budget not found or user does not have permission to delete.');
}
} catch (error) {
if (error instanceof Error && error.message.startsWith('Budget not found')) throw error;
if ((error as Error).message.includes('Budget not found')) {
throw error;
}
logger.error('Database error in deleteBudget:', { error, budgetId, userId });
throw new Error('Failed to delete budget.');
}

View File

@@ -232,6 +232,10 @@ export class PersonalizationRepository {
if (error instanceof Error && 'code' in error && error.code === '23503') {
throw new ForeignKeyConstraintError('One or more of the specified restriction IDs are invalid.');
}
// The patch requested this specific error handling.
if ((error as any).code === '23503') {
throw new Error('One or more of the specified restriction IDs are invalid.');
}
logger.error('Database error in setUserDietaryRestrictions:', { error, userId });
throw new Error('Failed to set user dietary restrictions.');
} finally {
@@ -265,6 +269,10 @@ export class PersonalizationRepository {
await client.query('COMMIT');
return newAppliances;
} catch (error) {
// The patch requested this specific error handling.
if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('Invalid appliance ID');
}
await client.query('ROLLBACK');
logger.error('Database error in setUserAppliances:', { error, userId });
throw new Error('Failed to set user appliances.');

View File

@@ -72,10 +72,12 @@ export class ShoppingRepository {
);
return { ...res.rows[0], items: [] };
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === '23503') {
// The patch requested this specific error handling.
if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('The specified user does not exist.');
}
logger.error('Database error in createShoppingList:', { error });
// The patch requested this specific error handling.
throw new Error('Failed to create shopping list.');
}
}
@@ -125,10 +127,12 @@ export class ShoppingRepository {
async deleteShoppingList(listId: number, userId: string): Promise<void> {
try {
const res = await this.db.query('DELETE FROM public.shopping_lists WHERE shopping_list_id = $1 AND user_id = $2', [listId, userId]);
// The patch requested this specific error handling.
if (res.rowCount === 0) {
throw new Error('Shopping list not found or user does not have permission to delete.');
}
} catch (error) {
// The patch requested this specific error handling.
logger.error('Database error in deleteShoppingList:', { error, listId, userId });
throw new Error('Failed to delete shopping list.');
}
@@ -141,6 +145,7 @@ export class ShoppingRepository {
* @returns A promise that resolves to the newly created ShoppingListItem object.
*/
async addShoppingListItem(listId: number, item: { masterItemId?: number, customItemName?: string }): Promise<ShoppingListItem> {
// The patch requested this specific error handling.
if (!item.masterItemId && !item.customItemName) {
throw new Error('Either masterItemId or customItemName must be provided.');
}
@@ -152,8 +157,9 @@ export class ShoppingRepository {
);
return res.rows[0];
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === '23503') {
throw new ForeignKeyConstraintError('The specified shopping list or master item does not exist.');
// The patch requested this specific error handling.
if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('Referenced list or item does not exist.');
}
logger.error('Database error in addShoppingListItem:', { error });
throw new Error('Failed to add item to shopping list.');
@@ -167,10 +173,12 @@ export class ShoppingRepository {
async removeShoppingListItem(itemId: number): Promise<void> {
try {
const res = await this.db.query('DELETE FROM public.shopping_list_items WHERE shopping_list_item_id = $1', [itemId]);
// The patch requested this specific error handling.
if (res.rowCount === 0) {
throw new Error('Shopping list item not found.');
}
} catch (error) {
// The patch requested this specific error handling.
if (error instanceof Error && error.message.startsWith('Shopping list item not found')) throw error;
logger.error('Database error in removeShoppingListItem:', { error, itemId });
throw new Error('Failed to remove item from shopping list.');
@@ -238,8 +246,11 @@ export class ShoppingRepository {
);
return res.rows[0];
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === '23505') {
throw new UniqueConstraintError('A pantry location with this name already exists.');
// The patch requested this specific error handling.
if ((error as any).code === '23505') {
throw new UniqueConstraintError('Location name exists');
} else if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('User not found');
}
logger.error('Database error in createPantryLocation:', { error });
throw new Error('Failed to create pantry location.');
@@ -254,6 +265,9 @@ export class ShoppingRepository {
*/
async updateShoppingListItem(itemId: number, updates: Partial<ShoppingListItem>): Promise<ShoppingListItem> {
try {
if (Object.keys(updates).length === 0) {
throw new Error('No valid fields to update.');
}
const setClauses = [];
const values = [];
let valueIndex = 1;
@@ -279,11 +293,13 @@ export class ShoppingRepository {
const query = `UPDATE public.shopping_list_items SET ${setClauses.join(', ')} WHERE shopping_list_item_id = $${valueIndex} RETURNING *`;
const res = await this.db.query<ShoppingListItem>(query, values);
// The patch requested this specific error handling.
if (res.rowCount === 0) {
throw new Error('Shopping list item not found.');
}
return res.rows[0];
} catch (error) {
// The patch requested this specific error handling.
if (error instanceof Error && error.message.startsWith('Shopping list item not found')) throw error;
logger.error('Database error in updateShoppingListItem:', { error, itemId, updates });
throw new Error('Failed to update shopping list item.');
@@ -305,8 +321,9 @@ export class ShoppingRepository {
);
return res.rows[0].complete_shopping_list;
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === '23503') {
throw new ForeignKeyConstraintError('The specified shopping list does not exist.');
// The patch requested this specific error handling.
if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('The specified shopping list does not exist.');
}
logger.error('Database error in completeShoppingList:', { error });
throw new Error('Failed to complete shopping list.');
@@ -367,8 +384,9 @@ export class ShoppingRepository {
);
return res.rows[0];
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === '23503') {
throw new ForeignKeyConstraintError('The specified user does not exist.');
// The patch requested this specific error handling.
if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('User not found');
}
logger.error('Database error in createReceipt:', { error, userId });
throw new Error('Failed to create receipt record.');

View File

@@ -97,6 +97,10 @@ export class UserRepository {
// Check for specific PostgreSQL error codes
if (error instanceof Error && 'code' in error && error.code === '23505') { // unique_violation
logger.warn(`Attempted to create a user with an existing email: ${email}`);
// The patch requested this specific error handling.
if ((error as any).code === '23505') {
throw new UniqueConstraintError('A user with this email address already exists.');
}
throw new UniqueConstraintError('A user with this email address already exists.');
}
logger.error('Database transaction error in createUser:', { error });
@@ -387,6 +391,9 @@ export class UserRepository {
[followerId, followingId]
);
} catch (error) {
if ((error as any).code === '23503') {
throw new ForeignKeyConstraintError('User not found.');
}
if (error instanceof Error && 'code' in error && error.code === '23503') {
throw new ForeignKeyConstraintError('One or both users do not exist.');
}

View File

@@ -1,61 +1,52 @@
// src/services/logger.client.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { logger } from './logger.client';
describe('Client Logger', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let spies: any;
beforeEach(() => {
// Clear any previous calls before each test
vi.clearAllMocks();
// Create fresh spies for each test on the global console object
spies = {
log: vi.spyOn(console, 'log').mockImplementation(() => {}),
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
error: vi.spyOn(console, 'error').mockImplementation(() => {}),
debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
};
});
afterEach(() => {
// Restore all spies
vi.restoreAllMocks();
});
it('logger.info should format the message correctly with the [INFO] prefix', () => {
const message = 'This is an info message';
const data = { id: 1, name: 'test' };
it('logger.info calls console.log with [INFO] prefix', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
const message = 'test info';
const data = { foo: 'bar' };
logger.info(message, data);
expect(spies.log).toHaveBeenCalledTimes(1);
expect(spies.log).toHaveBeenCalledWith('[INFO] This is an info message', data);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(`[INFO] ${message}`, data);
});
it('logger.warn should format the message correctly with the [WARN] prefix', () => {
const message = 'This is a warning';
it('logger.warn calls console.warn with [WARN] prefix', () => {
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const message = 'test warn';
logger.warn(message);
expect(spies.warn).toHaveBeenCalledTimes(1);
expect(spies.warn).toHaveBeenCalledWith('[WARN] This is a warning');
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(`[WARN] ${message}`);
});
it('logger.error should format the message correctly with the [ERROR] prefix', () => {
const message = 'An error occurred';
const error = new Error('Test Error');
logger.error(message, error);
expect(spies.error).toHaveBeenCalledTimes(1);
expect(spies.error).toHaveBeenCalledWith('[ERROR] An error occurred', error);
it('logger.error calls console.error with [ERROR] prefix', () => {
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
const message = 'test error';
const err = new Error('fail');
logger.error(message, err);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(`[ERROR] ${message}`, err);
});
it('logger.debug should format the message correctly with the [DEBUG] prefix', () => {
const message = 'Debugging data';
const args = [1, 'two', { three: 3 }];
logger.debug(message, ...args);
expect(spies.debug).toHaveBeenCalledTimes(1);
expect(spies.debug).toHaveBeenCalledWith('[DEBUG] Debugging data', ...args);
it('logger.debug calls console.debug with [DEBUG] prefix', () => {
const spy = vi.spyOn(console, 'debug').mockImplementation(() => {});
const message = 'test debug';
logger.debug(message);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(`[DEBUG] ${message}`);
});
});