phase 9 notification toggles (per-alert, per-category, master; default-disabled)
This commit is contained in:
102
services/notificationEnabled.js
Normal file
102
services/notificationEnabled.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Canonical enable/disable state accessor for per-alert notifications.
|
||||
*
|
||||
* State lives in two CONFIG keys:
|
||||
* - NOTIFICATIONS_MASTER_ENABLED (boolean) — global kill switch
|
||||
* - NOTIFICATION_ENABLED_JSON (JSON string → flat { [key]: boolean })
|
||||
*
|
||||
* Defaults: master off, every key off. Unknown keys in the JSON are ignored
|
||||
* on read (registry is the source of truth); keys missing from the JSON are
|
||||
* treated as false. Master off short-circuits every read — isEnabled never
|
||||
* returns true when master is off, so checkers bail without logs or metrics.
|
||||
*
|
||||
* Setters mutate CONFIG in memory and return the new value so the caller can
|
||||
* persist it via configPersistence.applyConfigUpdates. .env writes happen
|
||||
* there so schema validation and partial-success semantics stay consistent.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { CONFIG } = require('../config');
|
||||
const { REGISTRY } = require('./notificationRegistry');
|
||||
|
||||
function parseState() {
|
||||
const raw = CONFIG.NOTIFICATION_ENABLED_JSON;
|
||||
if (raw === undefined || raw === null || raw === '') return {};
|
||||
if (typeof raw === 'object' && !Array.isArray(raw)) return raw;
|
||||
try {
|
||||
const parsed = JSON.parse(String(raw));
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed;
|
||||
} catch (_) {}
|
||||
return {};
|
||||
}
|
||||
|
||||
function isMasterOn() {
|
||||
const v = CONFIG.NOTIFICATIONS_MASTER_ENABLED;
|
||||
return v === true || v === 'true';
|
||||
}
|
||||
|
||||
function isEnabled(alertKey) {
|
||||
if (!isMasterOn()) return false;
|
||||
const state = parseState();
|
||||
return state[alertKey] === true;
|
||||
}
|
||||
|
||||
function isCategoryEnabled(category) {
|
||||
if (!isMasterOn()) return false;
|
||||
const entries = REGISTRY[category];
|
||||
if (!Array.isArray(entries) || entries.length === 0) return false;
|
||||
const state = parseState();
|
||||
return entries.every(e => state[e.key] === true);
|
||||
}
|
||||
|
||||
function getAllState() {
|
||||
const state = parseState();
|
||||
const perKey = {};
|
||||
for (const entries of Object.values(REGISTRY)) {
|
||||
if (!Array.isArray(entries)) continue;
|
||||
for (const e of entries) {
|
||||
perKey[e.key] = state[e.key] === true;
|
||||
}
|
||||
}
|
||||
return { master: isMasterOn(), perKey };
|
||||
}
|
||||
|
||||
function serialize(state) {
|
||||
const ordered = {};
|
||||
Object.keys(state).sort().forEach(k => { ordered[k] = state[k] === true; });
|
||||
return JSON.stringify(ordered);
|
||||
}
|
||||
|
||||
function setKeyEnabled(key, enabled) {
|
||||
const state = parseState();
|
||||
state[String(key)] = enabled === true;
|
||||
const json = serialize(state);
|
||||
CONFIG.NOTIFICATION_ENABLED_JSON = json;
|
||||
return json;
|
||||
}
|
||||
|
||||
function setCategoryEnabled(category, enabled) {
|
||||
const state = parseState();
|
||||
const entries = REGISTRY[category];
|
||||
if (Array.isArray(entries)) {
|
||||
for (const e of entries) state[e.key] = enabled === true;
|
||||
}
|
||||
const json = serialize(state);
|
||||
CONFIG.NOTIFICATION_ENABLED_JSON = json;
|
||||
return json;
|
||||
}
|
||||
|
||||
function setMasterEnabled(enabled) {
|
||||
const value = enabled === true;
|
||||
CONFIG.NOTIFICATIONS_MASTER_ENABLED = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isEnabled,
|
||||
isCategoryEnabled,
|
||||
getAllState,
|
||||
setKeyEnabled,
|
||||
setCategoryEnabled,
|
||||
setMasterEnabled
|
||||
};
|
||||
Reference in New Issue
Block a user