refactor: rename email-server-worker to email-worker

Rename directory from email-server-worker to email-worker for clarity and brevity. Update all references in CLAUDE.md documentation.
This commit is contained in:
Knee Cola
2025-12-30 10:33:59 +01:00
parent 9d6ad17452
commit 3e4d8fb95c
37 changed files with 32 additions and 28 deletions

View File

@@ -0,0 +1,81 @@
import { ErrorRequestHandler, Request, Response } from "express";
import createHttpError, { HttpError } from "http-errors";
import { createLogger } from '../lib/logger';
import { NgitLocals } from "../types/NgitLocals";
import { failedRequestCounter } from "../lib/metricsCounters";
import { SupportedRoutes } from "../types/enums/SupportedRoutes";
const consoleLog = createLogger("server:server");
/**
* Router koji se zadnji poziva, a koji sastavlja odgovor u slučaju greške
* @param err
* @param req
* @param res
* @param next
*/
export const errorRouter:ErrorRequestHandler = async (err:HttpError, req, res, next) => {
const requestPath = req.path as SupportedRoutes;
// kako je ovaj error handler dosta složen, moguće je da negdje baci grešku
// > zato je zamotan u try-catch
// > na taj način osiguravam da neće srušiti cijeli proces
try {
let { name:errorLogName, message:errorLogText } = err;
let responseBody:string = "";
switch(err.status) {
case 400:
responseBody = 'bad request';
break;
case 401:
responseBody = 'unauthorized';
break;
case 403:
responseBody = 'forbidden';
break;
case 404:
consoleLog(`page not found ${req.method} ${requestPath}`)
responseBody = 'page not found';
errorLogText = `page ${requestPath} not found`;
break;
case 500:
responseBody = "internal server error";
errorLogText = err.message;
break;
default:
responseBody = err.name;
errorLogText = `err.status=${err.status};err.name=${err.name};err.message=${err.message}`;
}
consoleLog(`${errorLogName}:${errorLogText}`);
// `headersSent` će biti TRUE ako je router kod kojeg se dogodila greška već poslao header-e
// > ako ih probam ponovo postaviti, to će baciti grešku ... a to ovdje mogu izbjeći
if(!res.headersSent) {
res.status(err.status);
res.setHeader('Content-Type', "text/html");
res.end(responseBody);
} else {
// AKO nije pozvan `end` - pozovi ga i završi obradu zahtjeva
// ... u suprotnom će konekcija ostati otvorena do timeout-a
if(!res.writableEnded) {
res.end();
}
}
} catch(ex:any) {
// ovu grešku će obraditi `finalErrorRouter`
next(createHttpError(500, ex));
}
// ne mogu dopustiti da prometheus client sruši server
try {
failedRequestCounter.inc({ path: requestPath, status: err.status });
(res.locals as NgitLocals).stopPrometheusTimer({ path: req.path, status: err.status });
} catch(ex:any) {
console.error(ex);
}
};

View File

@@ -0,0 +1,34 @@
import { ErrorRequestHandler, Request, Response } from "express";
import { HttpError } from "http-errors";
import { createLogger } from '../lib/logger';
import { NgitLocals } from "../types/NgitLocals";
const consoleLog = createLogger("server:server");
/**
* Router koji se izvršava u slučaju grube greške koja nije obrađena nigdje prije
* @param err error objekt
* @param req express request
* @param res express response
* @param next
*/
export const finalErrorRouter:ErrorRequestHandler = async (err:HttpError, req, res, next) => {
const errorLogText:string = JSON.stringify({ message:err.message, name:err.name, stack:err.stack });
consoleLog(`Server Error ${err.status}\n${errorLogText}`);
// `headersSent` će biti TRUE ako je router kod kojeg se dogodila greška već poslao header-e
// > ako ih probam ponovo postaviti, to će baciti grešku i u ovom slučaju SRUŠITI SERVER - to ne smijemo dopustiti
if(!res.headersSent) {
res.status(err.status);
res.setHeader('Content-Type', "text/html");
res.end(`unhandled server error`);
} else {
// AKO nije pozvan `end` - pozovi ga i završi obradu zahtjeva
// ... u suprotnom će konekcija ostati otvorena do timeout-a
if(!res.writableEnded) {
res.end();
}
}
};

View File

@@ -0,0 +1,35 @@
import { RequestHandler, Router } from "express";
import { workerRunnerInfo } from "../workRunner";
import { coalesce } from "../lib/initTools";
const PULL_INTERVAL = parseInt(coalesce(process.env.PULL_INTERVAL, "10000"));
/** Maximum time between two worker jobs */
const MAX_WORKER_LATENCY = PULL_INTERVAL * 2.5;
/**
* Router koji se izvršava u slučaju grube greške koja nije obrađena nigdje prije
* @param req express request
* @param res express response
* @param next
*/
export const healthcheckRouter:RequestHandler = async (req, res, next) => {
const workerLatency = Date.now() - workerRunnerInfo.lastWorkTime;
if(workerLatency > MAX_WORKER_LATENCY) {
const msg = `No work done in ${workerLatency}ms. Last worker status = "${workerRunnerInfo.status}"`;
console.warn(msg)
res.status(500);
res.setHeader('Content-Type', 'text/plain');
res.end(msg);
} else {
res.status(200);
res.setHeader('Content-Type', 'text/plain');
res.end('OK');
}
};
export const pingRouter = Router();
pingRouter.get('/', healthcheckRouter);

View File

@@ -0,0 +1,19 @@
import { Router, NextFunction, Request, Response } from "express";
import createError from 'http-errors';
import { register } from 'prom-client';
import { createLogger } from '../lib/logger';
const logger = createLogger("server:metrics");
export const metricsRouter = Router();
metricsRouter.get('/', async (req:Request, res:Response, next:NextFunction) => {
// ne mogu dopustiti da prometheus client sruši server
try {
logger(`⚡️[server]: GET /metrics`);
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
} catch(ex:any) {
next(createError(500, (ex as Error).message));
}
});

View File

@@ -0,0 +1,16 @@
import { RequestHandler, Router } from "express";
/**
* Router koji se izvršava u slučaju grube greške koja nije obrađena nigdje prije
* @param req express request
* @param res express response
* @param next
*/
export const pingRequestHandler:RequestHandler = async (req, res, next) => {
res.status(200);
res.setHeader('Content-Type', 'text/plain');
res.end('PONG');
};
export const pingRouter = Router();
pingRouter.get('/', pingRequestHandler);