feat: enhance error handling and configuration validation
Some checks failed
CI / lint-and-test (push) Failing after 27s

- Added a global exception handler to log unhandled exceptions and return a generic 500 JSON response without exposing details to the client.
- Updated the configuration to validate the `DATABASE_URL` format, ensuring it starts with `sqlite://` or `postgresql://`, and log warnings for invalid formats.
- Introduced safe parsing for numeric environment variables (`HTTP_PORT`, `INIT_DATA_MAX_AGE_SECONDS`) with defaults on invalid values, including logging warnings for out-of-range values.
- Enhanced the duty schedule parser to enforce limits on the number of schedule rows and the length of full names and duty strings, raising appropriate errors when exceeded.
- Updated internationalization messages to include generic error responses for import failures and parsing issues, improving user experience.
- Added unit tests to verify the new error handling and configuration validation behaviors.
This commit is contained in:
2026-03-02 23:36:03 +03:00
parent 43386b15fa
commit 7ffa727832
20 changed files with 451 additions and 70 deletions

View File

@@ -40,6 +40,7 @@ export const MESSAGES = {
"event_type.duty": "Duty",
"event_type.unavailable": "Unavailable",
"event_type.vacation": "Vacation",
"event_type.other": "Other",
"duty.now_on_duty": "On duty now",
"duty.none_this_month": "No duties this month.",
"duty.today": "Today",
@@ -96,6 +97,7 @@ export const MESSAGES = {
"event_type.duty": "Дежурство",
"event_type.unavailable": "Недоступен",
"event_type.vacation": "Отпуск",
"event_type.other": "Другое",
"duty.now_on_duty": "Сейчас дежурит",
"duty.none_this_month": "В этом месяце дежурств нет.",
"duty.today": "Сегодня",
@@ -161,10 +163,30 @@ export function getLang() {
* @param {Record<string, string>} [params] - e.g. { time: '14:00' }
* @returns {string}
*/
/**
* Resolve event_type.* key with fallback: unknown types use event_type.duty or event_type.other.
* @param {'ru'|'en'} lang
* @param {string} key
* @returns {string}
*/
function tEventType(lang, key) {
const dict = MESSAGES[lang] || MESSAGES.en;
const enDict = MESSAGES.en;
let s = dict[key];
if (s === undefined) s = enDict[key];
if (s === undefined) {
s = dict["event_type.other"] || enDict["event_type.other"] || enDict["event_type.duty"] || key;
}
return s;
}
export function t(lang, key, params = {}) {
const dict = MESSAGES[lang] || MESSAGES.en;
let s = dict[key];
if (s === undefined) s = MESSAGES.en[key];
if (s === undefined && key.startsWith("event_type.")) {
return tEventType(lang, key);
}
if (s === undefined) return key;
Object.keys(params).forEach((k) => {
s = s.replace(new RegExp("\\{" + k + "\\}", "g"), params[k]);