oom issue
This commit is contained in:
@@ -50,177 +50,84 @@ describe('System Routes (/api/system)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /pm2-status', () => {
|
describe('GET /pm2-status', () => {
|
||||||
it('should return success: true when pm2 process is online', async () => {
|
// Helper function to set up the mock for `child_process.exec` for each test case.
|
||||||
// Arrange: Simulate a successful `pm2 describe` output for an online process.
|
// This avoids repeating the complex mock implementation in every test.
|
||||||
const pm2OnlineOutput = `
|
const setupExecMock = (error: ExecException | null, stdout: string, stderr: string) => {
|
||||||
|
vi.mocked(exec).mockImplementation(
|
||||||
|
(
|
||||||
|
command: string,
|
||||||
|
options?:
|
||||||
|
| ExecOptions
|
||||||
|
| ((error: ExecException | null, stdout: string, stderr: string) => void)
|
||||||
|
| null,
|
||||||
|
callback?: ((error: ExecException | null, stdout: string, stderr: string) => void) | null,
|
||||||
|
) => {
|
||||||
|
const actualCallback = (typeof options === 'function' ? options : callback) as (
|
||||||
|
error: ExecException | null,
|
||||||
|
stdout: string,
|
||||||
|
stderr: string,
|
||||||
|
) => void;
|
||||||
|
if (actualCallback) actualCallback(error, stdout, stderr);
|
||||||
|
return { unref: () => {} } as ReturnType<typeof exec>;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
description: 'should return success: true when pm2 process is online',
|
||||||
|
mock: {
|
||||||
|
error: null,
|
||||||
|
stdout: `
|
||||||
┌─ PM2 info ────────────────┐
|
┌─ PM2 info ────────────────┐
|
||||||
│ status │ online │
|
│ status │ online │
|
||||||
└───────────┴───────────┘
|
└───────────┴───────────┘
|
||||||
`;
|
`,
|
||||||
|
stderr: '',
|
||||||
type ExecCallback = (error: ExecException | null, stdout: string, stderr: string) => void;
|
|
||||||
|
|
||||||
// A robust mock for `exec` that handles its multiple overloads.
|
|
||||||
// This avoids the complex and error-prone `...args` signature.
|
|
||||||
vi.mocked(exec).mockImplementation(
|
|
||||||
(
|
|
||||||
command: string,
|
|
||||||
options?: ExecOptions | ExecCallback | null,
|
|
||||||
callback?: ExecCallback | null,
|
|
||||||
) => {
|
|
||||||
// The actual callback can be the second or third argument.
|
|
||||||
const actualCallback = (
|
|
||||||
typeof options === 'function' ? options : callback
|
|
||||||
) as ExecCallback;
|
|
||||||
if (actualCallback) {
|
|
||||||
actualCallback(null, pm2OnlineOutput, '');
|
|
||||||
}
|
|
||||||
// Return a minimal object that satisfies the ChildProcess type for .unref()
|
|
||||||
return { unref: () => {} } as ReturnType<typeof exec>;
|
|
||||||
},
|
},
|
||||||
);
|
expectedStatus: 200,
|
||||||
|
expectedBody: { success: true, message: 'Application is online and running under PM2.' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should return success: false when pm2 process is stopped',
|
||||||
|
mock: { error: null, stdout: '│ status │ stopped │', stderr: '' },
|
||||||
|
expectedStatus: 200,
|
||||||
|
expectedBody: { success: false, message: 'Application process exists but is not online.' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should return success: false when pm2 process does not exist',
|
||||||
|
mock: {
|
||||||
|
error: Object.assign(new Error('Command failed'), { code: 1 }),
|
||||||
|
stdout: "[PM2][ERROR] Process or Namespace flyer-crawler-api doesn't exist",
|
||||||
|
stderr: '',
|
||||||
|
},
|
||||||
|
expectedStatus: 200,
|
||||||
|
expectedBody: { success: false, message: 'Application process is not running under PM2.' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should return 500 if pm2 command produces stderr output',
|
||||||
|
mock: { error: null, stdout: 'Some stdout', stderr: 'A non-fatal warning occurred.' },
|
||||||
|
expectedStatus: 500,
|
||||||
|
expectedBody: { message: 'PM2 command produced an error: A non-fatal warning occurred.' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should return 500 on a generic exec error',
|
||||||
|
mock: { error: new Error('System error'), stdout: '', stderr: 'stderr output' },
|
||||||
|
expectedStatus: 500,
|
||||||
|
expectedBody: { message: 'System error' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(testCases)('$description', async ({ mock, expectedStatus, expectedBody }) => {
|
||||||
|
// Arrange
|
||||||
|
setupExecMock(mock.error as ExecException | null, mock.stdout, mock.stderr);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const response = await supertest(app).get('/api/system/pm2-status');
|
const response = await supertest(app).get('/api/system/pm2-status');
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(expectedStatus);
|
||||||
expect(response.body).toEqual({
|
expect(response.body).toEqual(expectedBody);
|
||||||
success: true,
|
|
||||||
message: 'Application is online and running under PM2.',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return success: false when pm2 process is stopped or errored', async () => {
|
|
||||||
const pm2StoppedOutput = `│ status │ stopped │`;
|
|
||||||
|
|
||||||
vi.mocked(exec).mockImplementation(
|
|
||||||
(
|
|
||||||
command: string,
|
|
||||||
options?:
|
|
||||||
| ExecOptions
|
|
||||||
| ((error: ExecException | null, stdout: string, stderr: string) => void)
|
|
||||||
| null,
|
|
||||||
callback?: ((error: ExecException | null, stdout: string, stderr: string) => void) | null,
|
|
||||||
) => {
|
|
||||||
const actualCallback = (typeof options === 'function' ? options : callback) as (
|
|
||||||
error: ExecException | null,
|
|
||||||
stdout: string,
|
|
||||||
stderr: string,
|
|
||||||
) => void;
|
|
||||||
if (actualCallback) {
|
|
||||||
actualCallback(null, pm2StoppedOutput, '');
|
|
||||||
}
|
|
||||||
return { unref: () => {} } as ReturnType<typeof exec>;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await supertest(app).get('/api/system/pm2-status');
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return success: false when pm2 process does not exist', async () => {
|
|
||||||
// Arrange: Simulate `pm2 describe` failing because the process isn't found.
|
|
||||||
const processNotFoundOutput =
|
|
||||||
"[PM2][ERROR] Process or Namespace flyer-crawler-api doesn't exist";
|
|
||||||
const processNotFoundError = new Error(
|
|
||||||
'Command failed: pm2 describe flyer-crawler-api',
|
|
||||||
) as ExecException;
|
|
||||||
processNotFoundError.code = 1;
|
|
||||||
|
|
||||||
vi.mocked(exec).mockImplementation(
|
|
||||||
(
|
|
||||||
command: string,
|
|
||||||
options?:
|
|
||||||
| ExecOptions
|
|
||||||
| ((error: ExecException | null, stdout: string, stderr: string) => void)
|
|
||||||
| null,
|
|
||||||
callback?: ((error: ExecException | null, stdout: string, stderr: string) => void) | null,
|
|
||||||
) => {
|
|
||||||
const actualCallback = (typeof options === 'function' ? options : callback) as (
|
|
||||||
error: ExecException | null,
|
|
||||||
stdout: string,
|
|
||||||
stderr: string,
|
|
||||||
) => void;
|
|
||||||
if (actualCallback) {
|
|
||||||
actualCallback(processNotFoundError, processNotFoundOutput, '');
|
|
||||||
}
|
|
||||||
return { unref: () => {} } as ReturnType<typeof exec>;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const response = await supertest(app).get('/api/system/pm2-status');
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.body).toEqual({
|
|
||||||
success: false,
|
|
||||||
message: 'Application process is not running under PM2.',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 500 if pm2 command produces stderr output', async () => {
|
|
||||||
// Arrange: Simulate a successful exit code but with content in stderr.
|
|
||||||
const stderrOutput = 'A non-fatal warning occurred.';
|
|
||||||
|
|
||||||
vi.mocked(exec).mockImplementation(
|
|
||||||
(
|
|
||||||
command: string,
|
|
||||||
options?:
|
|
||||||
| ExecOptions
|
|
||||||
| ((error: ExecException | null, stdout: string, stderr: string) => void)
|
|
||||||
| null,
|
|
||||||
callback?: ((error: ExecException | null, stdout: string, stderr: string) => void) | null,
|
|
||||||
) => {
|
|
||||||
const actualCallback = (typeof options === 'function' ? options : callback) as (
|
|
||||||
error: ExecException | null,
|
|
||||||
stdout: string,
|
|
||||||
stderr: string,
|
|
||||||
) => void;
|
|
||||||
if (actualCallback) {
|
|
||||||
actualCallback(null, 'Some stdout', stderrOutput);
|
|
||||||
}
|
|
||||||
return { unref: () => {} } as ReturnType<typeof exec>;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await supertest(app).get('/api/system/pm2-status');
|
|
||||||
expect(response.status).toBe(500);
|
|
||||||
expect(response.body.message).toBe(`PM2 command produced an error: ${stderrOutput}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 500 on a generic exec error', async () => {
|
|
||||||
vi.mocked(exec).mockImplementation(
|
|
||||||
(
|
|
||||||
command: string,
|
|
||||||
options?:
|
|
||||||
| ExecOptions
|
|
||||||
| ((error: ExecException | null, stdout: string, stderr: string) => void)
|
|
||||||
| null,
|
|
||||||
callback?: ((error: ExecException | null, stdout: string, stderr: string) => void) | null,
|
|
||||||
) => {
|
|
||||||
const actualCallback = (typeof options === 'function' ? options : callback) as (
|
|
||||||
error: ExecException | null,
|
|
||||||
stdout: string,
|
|
||||||
stderr: string,
|
|
||||||
) => void;
|
|
||||||
if (actualCallback) {
|
|
||||||
actualCallback(new Error('System error') as ExecException, '', 'stderr output');
|
|
||||||
}
|
|
||||||
return { unref: () => {} } as ReturnType<typeof exec>;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const response = await supertest(app).get('/api/system/pm2-status');
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(response.status).toBe(500);
|
|
||||||
expect(response.body.message).toBe('System error');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,14 @@ router.get(
|
|||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
// The name 'flyer-crawler-api' comes from your ecosystem.config.cjs file.
|
// The name 'flyer-crawler-api' comes from your ecosystem.config.cjs file.
|
||||||
exec('pm2 describe flyer-crawler-api', (error, stdout, stderr) => {
|
exec('pm2 describe flyer-crawler-api', (error, stdout, stderr) => {
|
||||||
|
// The "doesn't exist" message can appear in stdout or stderr depending on PM2 version and context.
|
||||||
|
const processNotFound =
|
||||||
|
stdout?.includes("doesn't exist") || stderr?.includes("doesn't exist");
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
// 'pm2 describe' exits with an error if the process is not found.
|
// 'pm2 describe' exits with an error if the process is not found.
|
||||||
// We can treat this as a "fail" status for our check.
|
// We can treat this as a "fail" status for our check.
|
||||||
if (stdout && stdout.includes("doesn't exist")) {
|
if (processNotFound) {
|
||||||
logger.warn('[API /pm2-status] PM2 process "flyer-crawler-api" not found.');
|
logger.warn('[API /pm2-status] PM2 process "flyer-crawler-api" not found.');
|
||||||
return res.json({
|
return res.json({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ export class UserRepository {
|
|||||||
);
|
);
|
||||||
return res.rows[0];
|
return res.rows[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, email }, 'Database error in findUserByEmail');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, email },
|
||||||
|
'Database error in findUserByEmail',
|
||||||
|
);
|
||||||
throw new Error('Failed to retrieve user from database.');
|
throw new Error('Failed to retrieve user from database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +130,10 @@ export class UserRepository {
|
|||||||
throw new UniqueConstraintError('A user with this email address already exists.');
|
throw new UniqueConstraintError('A user with this email address already exists.');
|
||||||
}
|
}
|
||||||
// The withTransaction helper logs the rollback, so we just log the context here.
|
// The withTransaction helper logs the rollback, so we just log the context here.
|
||||||
logger.error({ err: error, email }, 'Error during createUser transaction');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, email },
|
||||||
|
'Error during createUser transaction',
|
||||||
|
);
|
||||||
throw new Error('Failed to create user in database.');
|
throw new Error('Failed to create user in database.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -182,7 +188,10 @@ export class UserRepository {
|
|||||||
|
|
||||||
return authableProfile;
|
return authableProfile;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, email }, 'Database error in findUserWithProfileByEmail');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, email },
|
||||||
|
'Database error in findUserWithProfileByEmail',
|
||||||
|
);
|
||||||
throw new Error('Failed to retrieve user with profile from database.');
|
throw new Error('Failed to retrieve user with profile from database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +214,10 @@ export class UserRepository {
|
|||||||
return res.rows[0];
|
return res.rows[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof NotFoundError) throw error;
|
if (error instanceof NotFoundError) throw error;
|
||||||
logger.error({ err: error, userId }, 'Database error in findUserById');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in findUserById',
|
||||||
|
);
|
||||||
throw new Error('Failed to retrieve user by ID from database.');
|
throw new Error('Failed to retrieve user by ID from database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,7 +241,10 @@ export class UserRepository {
|
|||||||
return res.rows[0];
|
return res.rows[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof NotFoundError) throw error;
|
if (error instanceof NotFoundError) throw error;
|
||||||
logger.error({ err: error, userId }, 'Database error in findUserWithPasswordHashById');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in findUserWithPasswordHashById',
|
||||||
|
);
|
||||||
throw new Error('Failed to retrieve user with sensitive data by ID from database.');
|
throw new Error('Failed to retrieve user with sensitive data by ID from database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,7 +290,10 @@ export class UserRepository {
|
|||||||
if (error instanceof NotFoundError) {
|
if (error instanceof NotFoundError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
logger.error({ err: error, userId }, 'Database error in findUserProfileById');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in findUserProfileById',
|
||||||
|
);
|
||||||
throw new Error('Failed to retrieve user profile from database.');
|
throw new Error('Failed to retrieve user profile from database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,7 +339,10 @@ export class UserRepository {
|
|||||||
if (error instanceof NotFoundError) {
|
if (error instanceof NotFoundError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
logger.error({ err: error, userId, profileData }, 'Database error in updateUserProfile');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId, profileData },
|
||||||
|
'Database error in updateUserProfile',
|
||||||
|
);
|
||||||
throw new Error('Failed to update user profile in database.');
|
throw new Error('Failed to update user profile in database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,7 +371,10 @@ export class UserRepository {
|
|||||||
if (error instanceof NotFoundError) {
|
if (error instanceof NotFoundError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
logger.error({ err: error, userId, preferences }, 'Database error in updateUserPreferences');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId, preferences },
|
||||||
|
'Database error in updateUserPreferences',
|
||||||
|
);
|
||||||
throw new Error('Failed to update user preferences in database.');
|
throw new Error('Failed to update user preferences in database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +392,10 @@ export class UserRepository {
|
|||||||
[passwordHash, userId]
|
[passwordHash, userId]
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, userId }, 'Database error in updateUserPassword');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in updateUserPassword',
|
||||||
|
);
|
||||||
throw new Error('Failed to update user password in database.');
|
throw new Error('Failed to update user password in database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +409,10 @@ export class UserRepository {
|
|||||||
try {
|
try {
|
||||||
await this.db.query('DELETE FROM public.users WHERE user_id = $1', [userId]);
|
await this.db.query('DELETE FROM public.users WHERE user_id = $1', [userId]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, userId }, 'Database error in deleteUserById');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in deleteUserById',
|
||||||
|
);
|
||||||
throw new Error('Failed to delete user from database.');
|
throw new Error('Failed to delete user from database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +430,10 @@ export class UserRepository {
|
|||||||
[refreshToken, userId]
|
[refreshToken, userId]
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, userId }, 'Database error in saveRefreshToken');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in saveRefreshToken',
|
||||||
|
);
|
||||||
throw new Error('Failed to save refresh token.');
|
throw new Error('Failed to save refresh token.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,7 +456,10 @@ export class UserRepository {
|
|||||||
return res.rows[0];
|
return res.rows[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof NotFoundError) throw error;
|
if (error instanceof NotFoundError) throw error;
|
||||||
logger.error({ err: error }, 'Database error in findUserByRefreshToken');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error },
|
||||||
|
'Database error in findUserByRefreshToken',
|
||||||
|
);
|
||||||
throw new Error('Failed to find user by refresh token.'); // Generic error for other failures
|
throw new Error('Failed to find user by refresh token.'); // Generic error for other failures
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,7 +474,10 @@ export class UserRepository {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error }, 'Database error in deleteRefreshToken');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error },
|
||||||
|
'Database error in deleteRefreshToken',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +500,10 @@ export class UserRepository {
|
|||||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||||
throw new ForeignKeyConstraintError('The specified user does not exist.');
|
throw new ForeignKeyConstraintError('The specified user does not exist.');
|
||||||
}
|
}
|
||||||
logger.error({ err: error, userId }, 'Database error in createPasswordResetToken');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in createPasswordResetToken',
|
||||||
|
);
|
||||||
throw new Error('Failed to create password reset token.');
|
throw new Error('Failed to create password reset token.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,7 +520,10 @@ export class UserRepository {
|
|||||||
);
|
);
|
||||||
return res.rows;
|
return res.rows;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error }, 'Database error in getValidResetTokens');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error },
|
||||||
|
'Database error in getValidResetTokens',
|
||||||
|
);
|
||||||
throw new Error('Failed to retrieve valid reset tokens.');
|
throw new Error('Failed to retrieve valid reset tokens.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,7 +537,10 @@ export class UserRepository {
|
|||||||
try {
|
try {
|
||||||
await this.db.query('DELETE FROM public.password_reset_tokens WHERE token_hash = $1', [tokenHash]);
|
await this.db.query('DELETE FROM public.password_reset_tokens WHERE token_hash = $1', [tokenHash]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, tokenHash }, 'Database error in deleteResetToken');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, tokenHash },
|
||||||
|
'Database error in deleteResetToken',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,7 +559,10 @@ export class UserRepository {
|
|||||||
);
|
);
|
||||||
return res.rowCount ?? 0;
|
return res.rowCount ?? 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error }, 'Database error in deleteExpiredResetTokens');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error },
|
||||||
|
'Database error in deleteExpiredResetTokens',
|
||||||
|
);
|
||||||
throw new Error('Failed to delete expired password reset tokens.');
|
throw new Error('Failed to delete expired password reset tokens.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,7 +581,10 @@ export class UserRepository {
|
|||||||
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
if (error instanceof Error && 'code' in error && error.code === '23503') {
|
||||||
throw new ForeignKeyConstraintError('One or both users do not exist.');
|
throw new ForeignKeyConstraintError('One or both users do not exist.');
|
||||||
}
|
}
|
||||||
logger.error({ err: error, followerId, followingId }, 'Database error in followUser');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, followerId, followingId },
|
||||||
|
'Database error in followUser',
|
||||||
|
);
|
||||||
throw new Error('Failed to follow user.');
|
throw new Error('Failed to follow user.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -547,7 +601,10 @@ export class UserRepository {
|
|||||||
[followerId, followingId],
|
[followerId, followingId],
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, followerId, followingId }, 'Database error in unfollowUser');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, followerId, followingId },
|
||||||
|
'Database error in unfollowUser',
|
||||||
|
);
|
||||||
throw new Error('Failed to unfollow user.');
|
throw new Error('Failed to unfollow user.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -578,7 +635,10 @@ export class UserRepository {
|
|||||||
const res = await this.db.query<ActivityLogItem>(query, [userId, limit, offset]);
|
const res = await this.db.query<ActivityLogItem>(query, [userId, limit, offset]);
|
||||||
return res.rows;
|
return res.rows;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, userId, limit, offset }, 'Database error in getUserFeed');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId, limit, offset },
|
||||||
|
'Database error in getUserFeed',
|
||||||
|
);
|
||||||
throw new Error('Failed to retrieve user feed.');
|
throw new Error('Failed to retrieve user feed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,7 +660,10 @@ export class UserRepository {
|
|||||||
);
|
);
|
||||||
return res.rows[0];
|
return res.rows[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, queryData }, 'Database error in logSearchQuery');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, queryData },
|
||||||
|
'Database error in logSearchQuery',
|
||||||
|
);
|
||||||
throw new Error('Failed to log search query.');
|
throw new Error('Failed to log search query.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,7 +697,10 @@ export async function exportUserData(userId: string, logger: Logger): Promise<{
|
|||||||
return { profile, watchedItems, shoppingLists };
|
return { profile, watchedItems, shoppingLists };
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error, userId }, 'Database error in exportUserData');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error, userId },
|
||||||
|
'Database error in exportUserData',
|
||||||
|
);
|
||||||
throw new Error('Failed to export user data.');
|
throw new Error('Failed to export user data.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class GeocodingService {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err: error, cacheKey },
|
{ err: error instanceof Error ? error.message : error, cacheKey },
|
||||||
'Redis GET or JSON.parse command failed. Proceeding without cache.',
|
'Redis GET or JSON.parse command failed. Proceeding without cache.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ export class GeocodingService {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err: error },
|
{ err: error instanceof Error ? error.message : error },
|
||||||
'An error occurred while calling the Google Maps Geocoding API. Falling back to Nominatim.',
|
'An error occurred while calling the Google Maps Geocoding API. Falling back to Nominatim.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ export class GeocodingService {
|
|||||||
await redis.set(cacheKey, JSON.stringify(result), 'EX', 60 * 60 * 24 * 30); // Cache for 30 days
|
await redis.set(cacheKey, JSON.stringify(result), 'EX', 60 * 60 * 24 * 30); // Cache for 30 days
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err: error, cacheKey },
|
{ err: error instanceof Error ? error.message : error, cacheKey },
|
||||||
'Redis SET command failed. Result will not be cached.',
|
'Redis SET command failed. Result will not be cached.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,10 @@ export class GeocodingService {
|
|||||||
logger.info(`Successfully deleted ${totalDeleted} geocode cache entries.`);
|
logger.info(`Successfully deleted ${totalDeleted} geocode cache entries.`);
|
||||||
return totalDeleted;
|
return totalDeleted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error }, 'Failed to clear geocode cache from Redis.');
|
logger.error(
|
||||||
|
{ err: error instanceof Error ? error.message : error },
|
||||||
|
'Failed to clear geocode cache from Redis.',
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class GoogleGeocodingService {
|
|||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err: error, address },
|
{ err: error instanceof Error ? error.message : error, address },
|
||||||
'[GoogleGeocodingService] An error occurred while calling the Google Maps API.',
|
'[GoogleGeocodingService] An error occurred while calling the Google Maps API.',
|
||||||
);
|
);
|
||||||
throw error; // Re-throw to allow the calling service to handle the failure (e.g., by falling back).
|
throw error; // Re-throw to allow the calling service to handle the failure (e.g., by falling back).
|
||||||
|
|||||||
Reference in New Issue
Block a user