fix(notifications): replace Cloud Run job with worker service

This commit is contained in:
zouantchaw
2026-03-16 17:54:25 +01:00
parent 73287f42bd
commit 515a6f2bed
6 changed files with 206 additions and 22 deletions

View File

@@ -0,0 +1,46 @@
import express from 'express';
import pino from 'pino';
import pinoHttp from 'pino-http';
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
export function createWorkerApp({ dispatch = async () => ({}) } = {}) {
const app = express();
app.use(
pinoHttp({
logger,
})
);
app.use(express.json({ limit: '256kb' }));
app.get('/health', (_req, res) => {
res.status(200).json({ ok: true, service: 'notification-worker-v2' });
});
app.get('/readyz', (_req, res) => {
res.status(200).json({ ok: true, service: 'notification-worker-v2' });
});
app.post('/tasks/dispatch-notifications', async (req, res) => {
try {
const summary = await dispatch();
res.status(200).json({ ok: true, summary });
} catch (error) {
req.log?.error?.({ err: error }, 'notification dispatch failed');
res.status(500).json({
ok: false,
error: error?.message || String(error),
});
}
});
app.use((_req, res) => {
res.status(404).json({
code: 'NOT_FOUND',
message: 'Route not found',
});
});
return app;
}

View File

@@ -0,0 +1,12 @@
import { createWorkerApp } from './worker-app.js';
import { dispatchPendingNotifications } from './services/notification-dispatcher.js';
const port = Number(process.env.PORT || 8080);
const app = createWorkerApp({
dispatch: () => dispatchPendingNotifications(),
});
app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`krow-notification-worker listening on port ${port}`);
});

View File

@@ -0,0 +1,47 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import request from 'supertest';
import { createWorkerApp } from '../src/worker-app.js';
test('GET /readyz returns healthy response', async () => {
const app = createWorkerApp();
const res = await request(app).get('/readyz');
assert.equal(res.status, 200);
assert.equal(res.body.ok, true);
assert.equal(res.body.service, 'notification-worker-v2');
});
test('POST /tasks/dispatch-notifications returns dispatch summary', async () => {
const app = createWorkerApp({
dispatch: async () => ({
claimed: 2,
sent: 2,
}),
});
const res = await request(app)
.post('/tasks/dispatch-notifications')
.send({});
assert.equal(res.status, 200);
assert.equal(res.body.ok, true);
assert.equal(res.body.summary.claimed, 2);
assert.equal(res.body.summary.sent, 2);
});
test('POST /tasks/dispatch-notifications returns 500 on dispatch error', async () => {
const app = createWorkerApp({
dispatch: async () => {
throw new Error('dispatch exploded');
},
});
const res = await request(app)
.post('/tasks/dispatch-notifications')
.send({});
assert.equal(res.status, 500);
assert.equal(res.body.ok, false);
assert.match(res.body.error, /dispatch exploded/);
});