@better-forge/notify 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/alertPolicy.d.ts +41 -0
- package/dist/alertPolicy.d.ts.map +1 -0
- package/dist/alertPolicy.js +137 -0
- package/dist/alertPolicy.js.map +1 -0
- package/dist/channelInput.d.ts +34 -0
- package/dist/channelInput.d.ts.map +1 -0
- package/dist/channelInput.js +156 -0
- package/dist/channelInput.js.map +1 -0
- package/dist/channelSecrets.d.ts +8 -0
- package/dist/channelSecrets.d.ts.map +1 -0
- package/dist/channelSecrets.js +17 -0
- package/dist/channelSecrets.js.map +1 -0
- package/dist/engine.d.ts +68 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +314 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +907 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +86 -0
- package/dist/migrations.js.map +1 -0
- package/dist/notice.d.ts +55 -0
- package/dist/notice.d.ts.map +1 -0
- package/dist/notice.js +25 -0
- package/dist/notice.js.map +1 -0
- package/dist/routes.d.ts +74 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +302 -0
- package/dist/routes.js.map +1 -0
- package/dist/tables.d.ts +919 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +68 -0
- package/dist/tables.js.map +1 -0
- package/dist/template.d.ts +35 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +72 -0
- package/dist/template.js.map +1 -0
- package/dist/transports.d.ts +50 -0
- package/dist/transports.d.ts.map +1 -0
- package/dist/transports.js +274 -0
- package/dist/transports.js.map +1 -0
- package/dist/worker.d.ts +25 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +38 -0
- package/dist/worker.js.map +1 -0
- package/package.json +57 -4
- package/src/alertPolicy.ts +149 -0
- package/src/channelInput.ts +153 -0
- package/src/channelSecrets.ts +20 -0
- package/src/engine.ts +423 -0
- package/src/index.ts +16 -0
- package/src/migrations.ts +88 -0
- package/src/notice.ts +68 -0
- package/src/routes.ts +362 -0
- package/src/tables.ts +96 -0
- package/src/template.ts +76 -0
- package/src/transports.ts +351 -0
- package/src/worker.ts +58 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEzE,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,GAAG,kBAAkB,GAAG,cAAc,CAAC,GAAG,SAAS,aAAa,EAAE,CA2EpJ;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE/D"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defineNotifyTables } from './tables.js';
|
|
2
|
+
/** Notification channels, their delivery history and SMTP settings. Steps run in this order. */
|
|
3
|
+
export function notifyMigrations(options) {
|
|
4
|
+
const { sourceIdColumn, sourceNameColumn, smtpFromName } = options;
|
|
5
|
+
return [
|
|
6
|
+
{ kind: 'exec', sql: `
|
|
7
|
+
CREATE TABLE IF NOT EXISTS notification_channels (
|
|
8
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9
|
+
name TEXT NOT NULL,
|
|
10
|
+
type TEXT NOT NULL,
|
|
11
|
+
config TEXT NOT NULL DEFAULT '{}',
|
|
12
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
13
|
+
notify_on_recovery INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
created_at INTEGER NOT NULL,
|
|
15
|
+
updated_at INTEGER NOT NULL
|
|
16
|
+
);
|
|
17
|
+
` },
|
|
18
|
+
{ kind: 'exec', sql: `
|
|
19
|
+
CREATE TABLE IF NOT EXISTS notification_deliveries (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
channel_id INTEGER NOT NULL,
|
|
22
|
+
channel_name TEXT NOT NULL,
|
|
23
|
+
channel_type TEXT NOT NULL,
|
|
24
|
+
${sourceIdColumn} INTEGER,
|
|
25
|
+
${sourceNameColumn} TEXT NOT NULL,
|
|
26
|
+
event_type TEXT NOT NULL,
|
|
27
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
28
|
+
target_status TEXT NOT NULL,
|
|
29
|
+
previous_status TEXT NOT NULL,
|
|
30
|
+
variables TEXT NOT NULL,
|
|
31
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
33
|
+
next_attempt_at INTEGER,
|
|
34
|
+
last_attempt_at INTEGER,
|
|
35
|
+
delivered_at INTEGER,
|
|
36
|
+
last_error TEXT,
|
|
37
|
+
created_at INTEGER NOT NULL,
|
|
38
|
+
updated_at INTEGER NOT NULL
|
|
39
|
+
);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_notification_deliveries_status_next ON notification_deliveries(status, next_attempt_at);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_notification_deliveries_created ON notification_deliveries(created_at);
|
|
42
|
+
` },
|
|
43
|
+
{ kind: 'exec', sql: `
|
|
44
|
+
CREATE TABLE IF NOT EXISTS notification_delivery_attempts (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
delivery_id INTEGER NOT NULL,
|
|
47
|
+
attempt_number INTEGER NOT NULL,
|
|
48
|
+
status TEXT NOT NULL,
|
|
49
|
+
error TEXT,
|
|
50
|
+
started_at INTEGER NOT NULL,
|
|
51
|
+
completed_at INTEGER NOT NULL,
|
|
52
|
+
FOREIGN KEY (delivery_id) REFERENCES notification_deliveries(id) ON DELETE CASCADE
|
|
53
|
+
);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_notification_delivery_attempts_delivery ON notification_delivery_attempts(delivery_id, attempt_number);
|
|
55
|
+
` },
|
|
56
|
+
{ kind: 'exec', sql: `
|
|
57
|
+
CREATE TABLE IF NOT EXISTS smtp_settings (
|
|
58
|
+
id INTEGER PRIMARY KEY,
|
|
59
|
+
host TEXT NOT NULL DEFAULT '',
|
|
60
|
+
port INTEGER NOT NULL DEFAULT 587,
|
|
61
|
+
secure INTEGER NOT NULL DEFAULT 0,
|
|
62
|
+
user TEXT NOT NULL DEFAULT '',
|
|
63
|
+
password TEXT NOT NULL DEFAULT '',
|
|
64
|
+
from_address TEXT NOT NULL DEFAULT '',
|
|
65
|
+
from_name TEXT NOT NULL DEFAULT '${smtpFromName}',
|
|
66
|
+
updated_at INTEGER NOT NULL
|
|
67
|
+
);
|
|
68
|
+
` },
|
|
69
|
+
{ kind: 'column', sql: `ALTER TABLE smtp_settings ADD COLUMN vault_config TEXT`, desc: 'smtp_settings.vault_config' },
|
|
70
|
+
{ kind: 'column', sql: `ALTER TABLE notification_channels ADD COLUMN alert_policy TEXT NOT NULL DEFAULT '{}'`, desc: 'notification_channels.alert_policy' },
|
|
71
|
+
// SQLite cannot add a UNIQUE column. The application fills the keys of existing rows and then adds the unique index.
|
|
72
|
+
{ kind: 'column', sql: `ALTER TABLE notification_channels ADD COLUMN key TEXT NOT NULL DEFAULT ''`, desc: 'notification_channels.key' },
|
|
73
|
+
{ kind: 'column', sql: `ALTER TABLE notification_deliveries ADD COLUMN suppression_reason TEXT`, desc: 'notification_deliveries.suppression_reason' },
|
|
74
|
+
{ kind: 'column', sql: `ALTER TABLE notification_deliveries ADD COLUMN group_key TEXT`, desc: 'notification_deliveries.group_key' },
|
|
75
|
+
{ kind: 'exec', sql: `CREATE INDEX IF NOT EXISTS idx_notification_deliveries_group ON notification_deliveries(group_key, status)` },
|
|
76
|
+
{ kind: 'exec', sql: `CREATE INDEX IF NOT EXISTS idx_notification_deliveries_throttle ON notification_deliveries(channel_id, ${sourceIdColumn}, event_type, created_at)` },
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The notification tables and their migrations, for the application's list of database modules. The unique index on
|
|
81
|
+
* the channel \`key\` and the column that backs it are created by the application, once existing rows were given a key.
|
|
82
|
+
*/
|
|
83
|
+
export function createNotifyDbModule(options) {
|
|
84
|
+
return { id: 'notify', tables: defineNotifyTables(options), migrations: notifyMigrations(options) };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAA2B,MAAM,aAAa,CAAA;AAEzE,gGAAgG;AAChG,MAAM,UAAU,gBAAgB,CAAC,OAAyF;IACxH,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,GAAG,OAAO,CAAA;IAClE,OAAO;QACL,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;;;;;;;;;;;CAWxB,EAAE;QACC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;;;;;;IAMrB,cAAc;IACd,gBAAgB;;;;;;;;;;;;;;;;;CAiBnB,EAAE;QACC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;;;;;;;;;;;;CAYxB,EAAE;QACC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;;;;;;;;;qCASY,YAAY;;;CAGhD,EAAE;QACC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,wDAAwD,EAAE,IAAI,EAAE,4BAA4B,EAAE;QACrH,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,sFAAsF,EAAE,IAAI,EAAE,oCAAoC,EAAE;QAC3J,qHAAqH;QACrH,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,2EAA2E,EAAE,IAAI,EAAE,2BAA2B,EAAE;QACvI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,wEAAwE,EAAE,IAAI,EAAE,4CAA4C,EAAE;QACrJ,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,+DAA+D,EAAE,IAAI,EAAE,mCAAmC,EAAE;QACnI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,4GAA4G,EAAE;QACnI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,0GAA0G,cAAc,2BAA2B,EAAE;KAC3K,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA2B;IAC9D,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAqB,CAAA;AACxH,CAAC"}
|
package/dist/notice.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** How serious an event is; the rich channels paint it in a colour family and the push channels map it to a priority. */
|
|
2
|
+
export type NoticeSeverity = 'critical' | 'warning' | 'ok';
|
|
3
|
+
/**
|
|
4
|
+
* A piece of a headline. Plain text stays as written; a `subject` (what the event is about) and a `state` (what became
|
|
5
|
+
* of it) are the parts a channel emphasises in its own markup. A `kind` names what sort of thing the subject is
|
|
6
|
+
* ("Server"), which a channel may leave out where its title is already crowded.
|
|
7
|
+
*/
|
|
8
|
+
export type NoticeSegment = string | {
|
|
9
|
+
kind: string;
|
|
10
|
+
} | {
|
|
11
|
+
subject: string;
|
|
12
|
+
} | {
|
|
13
|
+
state: string;
|
|
14
|
+
};
|
|
15
|
+
export interface NoticeFact {
|
|
16
|
+
label: string;
|
|
17
|
+
value: string;
|
|
18
|
+
/** The label for channels that have room for a longer one. */
|
|
19
|
+
fullLabel?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* What the channels say about one event, independent of the application that raised it. The application builds it
|
|
23
|
+
* from its own variables; every channel turns it into its own payload.
|
|
24
|
+
*/
|
|
25
|
+
export interface Notice {
|
|
26
|
+
severity: NoticeSeverity;
|
|
27
|
+
headline: NoticeSegment[];
|
|
28
|
+
/** The line under the headline in channels that have one. */
|
|
29
|
+
subtitle: NoticeSegment[];
|
|
30
|
+
facts: NoticeFact[];
|
|
31
|
+
/** The error or details of the event, shown after the facts. */
|
|
32
|
+
detail?: {
|
|
33
|
+
label: string;
|
|
34
|
+
value: string;
|
|
35
|
+
};
|
|
36
|
+
/** When the event happened, as an ISO instant (anything else is shown as is). */
|
|
37
|
+
at: string;
|
|
38
|
+
/** Names `at` in the footer ("Checked at"). */
|
|
39
|
+
atLabel: string;
|
|
40
|
+
}
|
|
41
|
+
/** How a channel writes the emphasised segments of a headline. */
|
|
42
|
+
export interface SegmentMarkup {
|
|
43
|
+
/** Applied to every text segment, emphasised or not. */
|
|
44
|
+
text?: (value: string) => string;
|
|
45
|
+
/** Defaults to writing the kind as plain text. */
|
|
46
|
+
kind?: (value: string) => string;
|
|
47
|
+
subject: (value: string) => string;
|
|
48
|
+
state: (value: string) => string;
|
|
49
|
+
}
|
|
50
|
+
export declare function renderSegments(segments: NoticeSegment[], markup: SegmentMarkup): string;
|
|
51
|
+
/** A headline without any markup. */
|
|
52
|
+
export declare function plainSegments(segments: NoticeSegment[]): string;
|
|
53
|
+
/** "2026-10-09T17:00:22.988Z" → "2026-10-09 17:00:22 UTC"; anything that is not an ISO instant is shown as is. */
|
|
54
|
+
export declare function readableTimestamp(iso: string | undefined): string;
|
|
55
|
+
//# sourceMappingURL=notice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notice.d.ts","sourceRoot":"","sources":["../src/notice.ts"],"names":[],"mappings":"AAAA,yHAAyH;AACzH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,IAAI,CAAA;AAE1D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/F,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,cAAc,CAAA;IACxB,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,6DAA6D;IAC7D,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,gEAAgE;IAChE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzC,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAA;IACV,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAChC,kDAAkD;IAClD,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAChC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACjC;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAQvF;AAID,qCAAqC;AACrC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,CAE/D;AAED,kHAAkH;AAClH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAIjE"}
|
package/dist/notice.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function renderSegments(segments, markup) {
|
|
2
|
+
const text = markup.text ?? ((value) => value);
|
|
3
|
+
return segments.map((segment) => {
|
|
4
|
+
if (typeof segment === 'string')
|
|
5
|
+
return text(segment);
|
|
6
|
+
if ('kind' in segment)
|
|
7
|
+
return markup.kind ? markup.kind(text(segment.kind)) : text(segment.kind);
|
|
8
|
+
if ('subject' in segment)
|
|
9
|
+
return markup.subject(text(segment.subject));
|
|
10
|
+
return markup.state(text(segment.state));
|
|
11
|
+
}).join('');
|
|
12
|
+
}
|
|
13
|
+
const plainMarkup = { subject: (value) => value, state: (value) => value };
|
|
14
|
+
/** A headline without any markup. */
|
|
15
|
+
export function plainSegments(segments) {
|
|
16
|
+
return renderSegments(segments, plainMarkup);
|
|
17
|
+
}
|
|
18
|
+
/** "2026-10-09T17:00:22.988Z" → "2026-10-09 17:00:22 UTC"; anything that is not an ISO instant is shown as is. */
|
|
19
|
+
export function readableTimestamp(iso) {
|
|
20
|
+
if (!iso)
|
|
21
|
+
return 'unknown';
|
|
22
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})(?:\.\d+)?Z$/.exec(iso);
|
|
23
|
+
return match ? `${match[1]} ${match[2]} UTC` : iso;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=notice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notice.js","sourceRoot":"","sources":["../src/notice.ts"],"names":[],"mappings":"AA6CA,MAAM,UAAU,cAAc,CAAC,QAAyB,EAAE,MAAqB;IAC7E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,CAAA;IACtD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,CAAA;QACrD,IAAI,MAAM,IAAI,OAAO;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAChG,IAAI,SAAS,IAAI,OAAO;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;QACtE,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACb,CAAC;AAED,MAAM,WAAW,GAAkB,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAA;AAEzF,qCAAqC;AACrC,MAAM,UAAU,aAAa,CAAC,QAAyB;IACrD,OAAO,cAAc,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAA;AAC9C,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,iBAAiB,CAAC,GAAuB;IACvD,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAC1B,MAAM,KAAK,GAAG,sDAAsD,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC9E,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA;AACpD,CAAC"}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import { type Audit } from '@better-forge/audit';
|
|
3
|
+
import type { SqliteDatabase } from '@better-forge/db';
|
|
4
|
+
import { type RestoreResult } from '@better-forge/vault';
|
|
5
|
+
import type { NotifyTables } from './tables.js';
|
|
6
|
+
/** Result of choosing a key for a new channel: the key, or the HTTP status and message that refuse it. */
|
|
7
|
+
export type KeyResult = {
|
|
8
|
+
key: string;
|
|
9
|
+
} | {
|
|
10
|
+
status: 400 | 409;
|
|
11
|
+
error: string;
|
|
12
|
+
};
|
|
13
|
+
export interface NotifyRoutesOptions {
|
|
14
|
+
db: SqliteDatabase;
|
|
15
|
+
tables: NotifyTables;
|
|
16
|
+
audit: Audit;
|
|
17
|
+
/** The signed-in caller of a request (the routes sit behind the application's own guards). */
|
|
18
|
+
identity: (req: FastifyRequest) => {
|
|
19
|
+
userId: number;
|
|
20
|
+
email: string;
|
|
21
|
+
apiToken?: {
|
|
22
|
+
scopes: readonly string[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** Stored name of the grouping threshold in a channel's alert policy; it is also the name the API uses. */
|
|
26
|
+
minSourcesKey: string;
|
|
27
|
+
/** What the application calls the things notifications are about, as the API shows it. */
|
|
28
|
+
wire: {
|
|
29
|
+
/** Field and query parameter that carry the id of the thing a delivery is about. */
|
|
30
|
+
sourceId: string;
|
|
31
|
+
/** Field that carries its name. */
|
|
32
|
+
sourceName: string;
|
|
33
|
+
/** Plural, as written in the audit text of a grouping rule. */
|
|
34
|
+
sourcePlural: string;
|
|
35
|
+
};
|
|
36
|
+
/** Sender name the SMTP settings show before they are saved. */
|
|
37
|
+
defaultFromName: string;
|
|
38
|
+
/** The event types whose history this caller may not read (they are left out of the list and answer 404). */
|
|
39
|
+
hiddenEventTypes: (req: FastifyRequest) => readonly string[];
|
|
40
|
+
/** The secrets of a channel's configuration: masked on the way out, restored when a mask comes back. */
|
|
41
|
+
secrets: {
|
|
42
|
+
mask: (config: unknown) => unknown;
|
|
43
|
+
restore: (incoming: unknown, stored: unknown) => RestoreResult;
|
|
44
|
+
};
|
|
45
|
+
/** Keeps a token that may not use vault secrets away from channels that reference one. */
|
|
46
|
+
vaultUse: {
|
|
47
|
+
refused: string;
|
|
48
|
+
problem: (token: {
|
|
49
|
+
scopes: readonly string[];
|
|
50
|
+
} | undefined, incoming: unknown, stored: unknown) => string | null;
|
|
51
|
+
};
|
|
52
|
+
/** Chooses the key of a new channel (the requested one if it is free, else one made from the name). */
|
|
53
|
+
resolveKey: (name: string, requested: unknown, ownerOf: (key: string) => Promise<number | undefined>) => Promise<KeyResult>;
|
|
54
|
+
/** Called before a channel is deleted, to remove whatever links the application keeps to it. */
|
|
55
|
+
onChannelDeleted: (channelId: number) => Promise<void>;
|
|
56
|
+
/** Retries a failed delivery. */
|
|
57
|
+
retryDelivery: (deliveryId: number) => Promise<boolean>;
|
|
58
|
+
/** Sends a test notification through a channel. */
|
|
59
|
+
testChannel: (channelId: number) => Promise<{
|
|
60
|
+
ok: boolean;
|
|
61
|
+
error?: string;
|
|
62
|
+
}>;
|
|
63
|
+
/** Sends a test mail through the SMTP settings. */
|
|
64
|
+
testSmtp: (to: string) => Promise<{
|
|
65
|
+
ok: boolean;
|
|
66
|
+
error?: string;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The admin routes of the notifications: delivery history and retry, channel management and SMTP settings. A plain
|
|
71
|
+
* plugin, so the hooks of the scope it is registered in apply to it.
|
|
72
|
+
*/
|
|
73
|
+
export declare function createNotifyRoutes(options: NotifyRoutesOptions): (app: FastifyInstance) => Promise<void>;
|
|
74
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { and, desc, eq, gte, lte, ne, sql } from 'drizzle-orm';
|
|
2
|
+
import { parsePagination } from '@better-forge/audit';
|
|
3
|
+
import { tokenAllows, VAULT_USE_SCOPE } from '@better-forge/contracts';
|
|
4
|
+
import { canonical } from '@better-forge/vault';
|
|
5
|
+
import { createAlertPolicy } from './alertPolicy.js';
|
|
6
|
+
import { CHANNEL_TYPES, channelConfigError } from './channelInput.js';
|
|
7
|
+
const SMTP_PASSWORD_MASK = '••••••••';
|
|
8
|
+
const isConfigObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
9
|
+
/**
|
|
10
|
+
* The admin routes of the notifications: delivery history and retry, channel management and SMTP settings. A plain
|
|
11
|
+
* plugin, so the hooks of the scope it is registered in apply to it.
|
|
12
|
+
*/
|
|
13
|
+
export function createNotifyRoutes(options) {
|
|
14
|
+
const { db, audit, wire, identity, secrets, vaultUse } = options;
|
|
15
|
+
const { notificationChannels, notificationDeliveries, notificationDeliveryAttempts, smtpSettings } = options.tables;
|
|
16
|
+
const policy = createAlertPolicy({ minSourcesKey: options.minSourcesKey });
|
|
17
|
+
const parseAlertPolicy = (stored) => policy.toWire(policy.parseAlertPolicy(stored)); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
18
|
+
const normalizeAlertPolicy = (input) => policy.toWire(policy.normalizeAlertPolicy(input));
|
|
19
|
+
/** A delivery as the API shows it: what it is about under the application's names, every other key where it was. */
|
|
20
|
+
function wireDelivery(row) {
|
|
21
|
+
return Object.fromEntries(Object.entries(row).map(([key, value]) => [
|
|
22
|
+
key === 'sourceId' ? wire.sourceId : key === 'sourceName' ? wire.sourceName : key, value,
|
|
23
|
+
]));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Channels leave the API with both JSON columns expanded, the policy filled in with defaults and secrets masked.
|
|
27
|
+
* The form sends a mask back when the secret was not edited, and the stored value is kept, as long as the channel
|
|
28
|
+
* keeps its type.
|
|
29
|
+
*/
|
|
30
|
+
function parseChannel(r) {
|
|
31
|
+
return { ...r, config: secrets.mask(JSON.parse(r.config)), alertPolicy: parseAlertPolicy(r.alertPolicy) };
|
|
32
|
+
}
|
|
33
|
+
/** Flat, auditable view of the hygiene settings - nested JSON would produce useless diffs. */
|
|
34
|
+
function policyAuditFields(r) {
|
|
35
|
+
const p = parseAlertPolicy(r.alertPolicy);
|
|
36
|
+
return {
|
|
37
|
+
quietHours: p.quietHours.enabled ? `${p.quietHours.start}–${p.quietHours.end} ${p.quietHours.timezone} (${p.quietHours.mode})` : 'off',
|
|
38
|
+
throttle: p.throttle.enabled ? `${p.throttle.maxAlerts} alerts / ${p.throttle.windowMinutes} min` : 'off',
|
|
39
|
+
grouping: p.grouping.enabled ? `${p.grouping[options.minSourcesKey]}+ ${wire.sourcePlural} / ${p.grouping.windowSeconds} s` : 'off',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const channelKeyOwner = async (key) => (await db.select({ id: notificationChannels.id }).from(notificationChannels).where(eq(notificationChannels.key, key)))[0]?.id;
|
|
43
|
+
return async function notifyRoutes(app) {
|
|
44
|
+
const actor = (req) => audit.actor(identity(req));
|
|
45
|
+
app.get('/deliveries', async (req) => {
|
|
46
|
+
const { page, limit, offset } = parsePagination(req.query, { defaultLimit: 25, maxLimit: 100 });
|
|
47
|
+
const query = req.query;
|
|
48
|
+
const conditions = [];
|
|
49
|
+
if (query['status'])
|
|
50
|
+
conditions.push(eq(notificationDeliveries.status, query['status']));
|
|
51
|
+
if (query['channelId'])
|
|
52
|
+
conditions.push(eq(notificationDeliveries.channelId, Number(query['channelId'])));
|
|
53
|
+
if (query['channelType'])
|
|
54
|
+
conditions.push(eq(notificationDeliveries.channelType, query['channelType']));
|
|
55
|
+
if (query[wire.sourceId])
|
|
56
|
+
conditions.push(eq(notificationDeliveries.sourceId, Number(query[wire.sourceId])));
|
|
57
|
+
if (query['eventType'])
|
|
58
|
+
conditions.push(eq(notificationDeliveries.eventType, query['eventType']));
|
|
59
|
+
if (query['from'])
|
|
60
|
+
conditions.push(gte(notificationDeliveries.createdAt, Number(query['from'])));
|
|
61
|
+
if (query['to'])
|
|
62
|
+
conditions.push(lte(notificationDeliveries.createdAt, Number(query['to'])));
|
|
63
|
+
for (const eventType of options.hiddenEventTypes(req))
|
|
64
|
+
conditions.push(ne(notificationDeliveries.eventType, eventType));
|
|
65
|
+
const where = conditions.length ? and(...conditions) : undefined;
|
|
66
|
+
const [rows, count] = await Promise.all([
|
|
67
|
+
db.select({
|
|
68
|
+
id: notificationDeliveries.id, channelId: notificationDeliveries.channelId,
|
|
69
|
+
channelName: notificationDeliveries.channelName, channelType: notificationDeliveries.channelType,
|
|
70
|
+
sourceId: notificationDeliveries.sourceId, sourceName: notificationDeliveries.sourceName,
|
|
71
|
+
eventType: notificationDeliveries.eventType, status: notificationDeliveries.status,
|
|
72
|
+
targetStatus: notificationDeliveries.targetStatus, previousStatus: notificationDeliveries.previousStatus,
|
|
73
|
+
attemptCount: notificationDeliveries.attemptCount, maxAttempts: notificationDeliveries.maxAttempts,
|
|
74
|
+
nextAttemptAt: notificationDeliveries.nextAttemptAt, lastAttemptAt: notificationDeliveries.lastAttemptAt,
|
|
75
|
+
deliveredAt: notificationDeliveries.deliveredAt, lastError: notificationDeliveries.lastError,
|
|
76
|
+
suppressionReason: notificationDeliveries.suppressionReason, groupKey: notificationDeliveries.groupKey,
|
|
77
|
+
createdAt: notificationDeliveries.createdAt, updatedAt: notificationDeliveries.updatedAt,
|
|
78
|
+
}).from(notificationDeliveries).where(where).orderBy(desc(notificationDeliveries.createdAt)).limit(limit).offset(offset),
|
|
79
|
+
db.select({ count: sql `count(*)` }).from(notificationDeliveries).where(where),
|
|
80
|
+
]);
|
|
81
|
+
const total = count[0]?.count ?? 0;
|
|
82
|
+
return { deliveries: rows.map(wireDelivery), total, page, limit, pages: Math.ceil(total / limit) };
|
|
83
|
+
});
|
|
84
|
+
app.get('/deliveries/:id', async (req, reply) => {
|
|
85
|
+
const id = Number(req.params.id);
|
|
86
|
+
const delivery = (await db.select().from(notificationDeliveries).where(eq(notificationDeliveries.id, id)))[0];
|
|
87
|
+
if (!delivery || options.hiddenEventTypes(req).includes(delivery.eventType))
|
|
88
|
+
return reply.code(404).send({ error: 'Delivery not found' });
|
|
89
|
+
const attempts = await db.select().from(notificationDeliveryAttempts)
|
|
90
|
+
.where(eq(notificationDeliveryAttempts.deliveryId, id)).orderBy(desc(notificationDeliveryAttempts.attemptNumber));
|
|
91
|
+
return { ...wireDelivery(delivery), variables: undefined, attempts };
|
|
92
|
+
});
|
|
93
|
+
app.post('/deliveries/:id/retry', async (req, reply) => {
|
|
94
|
+
const id = Number(req.params.id);
|
|
95
|
+
const existing = (await db.select().from(notificationDeliveries).where(eq(notificationDeliveries.id, id)))[0];
|
|
96
|
+
if (!existing || options.hiddenEventTypes(req).includes(existing.eventType))
|
|
97
|
+
return reply.code(404).send({ error: 'Delivery not found' });
|
|
98
|
+
if (existing.status !== 'failed')
|
|
99
|
+
return reply.code(409).send({ error: 'Only failed deliveries can be retried' });
|
|
100
|
+
await options.retryDelivery(id);
|
|
101
|
+
await audit.write(actor(req), 'update', 'notification_delivery', id, `${existing.channelName} · ${existing.sourceName}`, {
|
|
102
|
+
manualRetry: { from: false, to: true },
|
|
103
|
+
});
|
|
104
|
+
const row = (await db.select().from(notificationDeliveries).where(eq(notificationDeliveries.id, id)))[0];
|
|
105
|
+
// The row may be gone already (the retention purge); the answer is then empty, as it has always been.
|
|
106
|
+
return row && wireDelivery(row);
|
|
107
|
+
});
|
|
108
|
+
// ── Channels CRUD ──────────────────────────────────────────────────────
|
|
109
|
+
app.get('/channels', async () => {
|
|
110
|
+
const rows = await db.select().from(notificationChannels);
|
|
111
|
+
return rows.map(parseChannel);
|
|
112
|
+
});
|
|
113
|
+
app.post('/channels', async (req, reply) => {
|
|
114
|
+
if (typeof req.body?.name !== 'string' || !req.body.name.trim())
|
|
115
|
+
return reply.code(400).send({ error: 'Name is required' });
|
|
116
|
+
if (!CHANNEL_TYPES.includes(req.body.type)) {
|
|
117
|
+
return reply.code(400).send({ error: `Type must be one of: ${CHANNEL_TYPES.join(', ')}` });
|
|
118
|
+
}
|
|
119
|
+
if (req.body.config !== undefined && req.body.config !== null && !isConfigObject(req.body.config)) {
|
|
120
|
+
return reply.code(400).send({ error: 'config must be an object' });
|
|
121
|
+
}
|
|
122
|
+
const restored = secrets.restore(req.body.config ?? {}, undefined);
|
|
123
|
+
if ('error' in restored)
|
|
124
|
+
return reply.code(400).send({ error: restored.error });
|
|
125
|
+
const vaultProblem = vaultUse.problem(identity(req).apiToken, restored.config, undefined);
|
|
126
|
+
if (vaultProblem)
|
|
127
|
+
return reply.code(403).send({ error: vaultProblem });
|
|
128
|
+
const configProblem = channelConfigError(req.body.type, restored.config);
|
|
129
|
+
if (configProblem)
|
|
130
|
+
return reply.code(400).send({ error: configProblem });
|
|
131
|
+
const resolvedKey = await options.resolveKey(req.body.name, req.body.key, channelKeyOwner);
|
|
132
|
+
if ('error' in resolvedKey)
|
|
133
|
+
return reply.code(resolvedKey.status).send({ error: resolvedKey.error });
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const results = await db.insert(notificationChannels).values({
|
|
136
|
+
key: resolvedKey.key,
|
|
137
|
+
name: req.body.name,
|
|
138
|
+
type: req.body.type,
|
|
139
|
+
config: JSON.stringify(restored.config),
|
|
140
|
+
enabled: req.body.enabled ?? 1,
|
|
141
|
+
notifyOnRecovery: req.body.notifyOnRecovery ?? 0,
|
|
142
|
+
alertPolicy: JSON.stringify(normalizeAlertPolicy(req.body.alertPolicy)),
|
|
143
|
+
createdAt: now,
|
|
144
|
+
updatedAt: now,
|
|
145
|
+
}).returning();
|
|
146
|
+
const r = results[0];
|
|
147
|
+
audit.write(actor(req), 'create', 'notification_channel', r.id, r.name, audit.snapshot({ name: r.name, type: r.type, enabled: r.enabled, notifyOnRecovery: r.notifyOnRecovery, ...policyAuditFields(r) }));
|
|
148
|
+
return parseChannel(r);
|
|
149
|
+
});
|
|
150
|
+
app.get('/channels/:id', async (req, reply) => {
|
|
151
|
+
const r = (await db.select().from(notificationChannels).where(eq(notificationChannels.id, Number(req.params.id))))[0];
|
|
152
|
+
if (!r)
|
|
153
|
+
return reply.code(404).send({ error: 'Not found' });
|
|
154
|
+
return parseChannel(r);
|
|
155
|
+
});
|
|
156
|
+
app.patch('/channels/:id', async (req, reply) => {
|
|
157
|
+
const id = Number(req.params.id);
|
|
158
|
+
const existing = (await db.select().from(notificationChannels).where(eq(notificationChannels.id, id)))[0];
|
|
159
|
+
if (!existing)
|
|
160
|
+
return reply.code(404).send({ error: 'Not found' });
|
|
161
|
+
if (req.body.type !== undefined && !CHANNEL_TYPES.includes(req.body.type)) {
|
|
162
|
+
return reply.code(400).send({ error: `Type must be one of: ${CHANNEL_TYPES.join(', ')}` });
|
|
163
|
+
}
|
|
164
|
+
// The stored config belongs to the old type; keeping it under another one would also change what is masked.
|
|
165
|
+
if (req.body.type !== undefined && req.body.type !== existing.type && req.body.config === undefined) {
|
|
166
|
+
return reply.code(400).send({ error: 'Changing the type needs a new config in the same request' });
|
|
167
|
+
}
|
|
168
|
+
if (req.body.config !== undefined && !isConfigObject(req.body.config)) {
|
|
169
|
+
return reply.code(400).send({ error: 'config must be an object' });
|
|
170
|
+
}
|
|
171
|
+
if (req.body.key !== undefined)
|
|
172
|
+
return reply.code(400).send({ error: 'The key is set when the object is created and cannot be changed' });
|
|
173
|
+
const effectiveType = req.body.type ?? existing.type;
|
|
174
|
+
let config;
|
|
175
|
+
if (req.body.config !== undefined) {
|
|
176
|
+
const stored = effectiveType === existing.type ? JSON.parse(existing.config) : undefined;
|
|
177
|
+
const restored = secrets.restore(req.body.config, stored);
|
|
178
|
+
if ('error' in restored)
|
|
179
|
+
return reply.code(400).send({ error: restored.error });
|
|
180
|
+
const vaultProblem = vaultUse.problem(identity(req).apiToken, restored.config, stored);
|
|
181
|
+
if (vaultProblem)
|
|
182
|
+
return reply.code(403).send({ error: vaultProblem });
|
|
183
|
+
config = restored.config;
|
|
184
|
+
}
|
|
185
|
+
if (config !== undefined || req.body.type !== undefined) {
|
|
186
|
+
const configProblem = channelConfigError(effectiveType, config ?? JSON.parse(existing.config));
|
|
187
|
+
if (configProblem)
|
|
188
|
+
return reply.code(400).send({ error: configProblem });
|
|
189
|
+
}
|
|
190
|
+
const updates = { updatedAt: Date.now() };
|
|
191
|
+
if (req.body.name !== undefined)
|
|
192
|
+
updates.name = req.body.name;
|
|
193
|
+
if (req.body.type !== undefined)
|
|
194
|
+
updates.type = req.body.type;
|
|
195
|
+
if (req.body.config !== undefined)
|
|
196
|
+
updates.config = JSON.stringify(config);
|
|
197
|
+
if (req.body.enabled !== undefined)
|
|
198
|
+
updates.enabled = req.body.enabled;
|
|
199
|
+
if (req.body.notifyOnRecovery !== undefined)
|
|
200
|
+
updates.notifyOnRecovery = req.body.notifyOnRecovery;
|
|
201
|
+
if (req.body.alertPolicy !== undefined)
|
|
202
|
+
updates.alertPolicy = JSON.stringify(normalizeAlertPolicy(req.body.alertPolicy));
|
|
203
|
+
const results = await db.update(notificationChannels).set(updates).where(eq(notificationChannels.id, id)).returning();
|
|
204
|
+
const r = results[0];
|
|
205
|
+
const before = { name: existing.name, type: existing.type, enabled: existing.enabled, notifyOnRecovery: existing.notifyOnRecovery, ...policyAuditFields(existing) };
|
|
206
|
+
const after = { name: r.name, type: r.type, enabled: r.enabled, notifyOnRecovery: r.notifyOnRecovery, ...policyAuditFields(r) };
|
|
207
|
+
const diff = audit.diffObjects(before, after);
|
|
208
|
+
if (req.body.config !== undefined)
|
|
209
|
+
diff['config'] = { from: '[previous config]', to: '[updated config]' };
|
|
210
|
+
if (Object.keys(diff).length)
|
|
211
|
+
audit.write(actor(req), 'update', 'notification_channel', id, existing.name, diff);
|
|
212
|
+
return parseChannel(r);
|
|
213
|
+
});
|
|
214
|
+
app.delete('/channels/:id', async (req, reply) => {
|
|
215
|
+
const id = Number(req.params.id);
|
|
216
|
+
const existing = (await db.select().from(notificationChannels).where(eq(notificationChannels.id, id)))[0];
|
|
217
|
+
await options.onChannelDeleted(id);
|
|
218
|
+
await db.delete(notificationChannels).where(eq(notificationChannels.id, id));
|
|
219
|
+
if (existing) {
|
|
220
|
+
audit.write(actor(req), 'delete', 'notification_channel', id, existing.name, audit.snapshot({ name: existing.name, type: existing.type }));
|
|
221
|
+
}
|
|
222
|
+
return reply.code(204).send();
|
|
223
|
+
});
|
|
224
|
+
app.post('/channels/:id/test', async (req, reply) => {
|
|
225
|
+
const result = await options.testChannel(Number(req.params.id));
|
|
226
|
+
if (!result.ok)
|
|
227
|
+
return reply.code(422).send({ error: result.error });
|
|
228
|
+
return { ok: true };
|
|
229
|
+
});
|
|
230
|
+
// ── SMTP Settings ──────────────────────────────────────────────────────
|
|
231
|
+
app.get('/smtp', async () => {
|
|
232
|
+
const row = (await db.select().from(smtpSettings))[0];
|
|
233
|
+
if (!row)
|
|
234
|
+
return { host: '', port: 587, secure: 0, user: '', password: '', fromAddress: '', fromName: options.defaultFromName, vault: null, updatedAt: 0 };
|
|
235
|
+
return {
|
|
236
|
+
...row,
|
|
237
|
+
password: row.password ? SMTP_PASSWORD_MASK : '',
|
|
238
|
+
vault: row.vaultConfig ? JSON.parse(row.vaultConfig) : null,
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
app.put('/smtp', async (req, reply) => {
|
|
242
|
+
const now = Date.now();
|
|
243
|
+
const existing = (await db.select().from(smtpSettings))[0];
|
|
244
|
+
// A vault secret is sent to the SMTP server: a token without the permission to use vault secrets may keep the
|
|
245
|
+
// reference and server that are saved, but not set or change them.
|
|
246
|
+
const token = identity(req).apiToken;
|
|
247
|
+
if (token && req.body.vault && !tokenAllows(token.scopes, VAULT_USE_SCOPE)) {
|
|
248
|
+
const saved = existing?.vaultConfig ?? null;
|
|
249
|
+
const sameReference = saved !== null && canonical(JSON.parse(saved)) === canonical(req.body.vault);
|
|
250
|
+
const sameServer = !!existing && req.body.host === existing.host && Number(req.body.port) === existing.port;
|
|
251
|
+
const sameTransport = !!existing && Number(req.body.secure) === Number(existing.secure) && (req.body.user ?? '') === existing.user;
|
|
252
|
+
if (!sameReference || !sameServer || !sameTransport)
|
|
253
|
+
return reply.code(403).send({ error: vaultUse.refused });
|
|
254
|
+
}
|
|
255
|
+
const passwordSupplied = !!req.body.password && req.body.password !== SMTP_PASSWORD_MASK;
|
|
256
|
+
// Never forward the stored password to a different server or account: changing where it goes
|
|
257
|
+
// requires re-entering it.
|
|
258
|
+
if (existing?.password && !req.body.vault && !passwordSupplied && (req.body.host !== existing.host || Number(req.body.port) !== existing.port || (req.body.user ?? '') !== existing.user)) {
|
|
259
|
+
return reply.code(400).send({ error: 'Re-enter the SMTP password when changing the host, port or username' });
|
|
260
|
+
}
|
|
261
|
+
const values = {
|
|
262
|
+
host: req.body.host,
|
|
263
|
+
port: req.body.port,
|
|
264
|
+
secure: req.body.secure,
|
|
265
|
+
user: req.body.vault ? '' : (req.body.user ?? ''),
|
|
266
|
+
fromAddress: req.body.fromAddress,
|
|
267
|
+
fromName: req.body.fromName,
|
|
268
|
+
vaultConfig: req.body.vault ? JSON.stringify(req.body.vault) : null,
|
|
269
|
+
updatedAt: now,
|
|
270
|
+
};
|
|
271
|
+
if (existing) {
|
|
272
|
+
const password = req.body.vault
|
|
273
|
+
? ''
|
|
274
|
+
: (passwordSupplied ? req.body.password : existing.password);
|
|
275
|
+
await db.update(smtpSettings).set({ ...values, password }).where(eq(smtpSettings.id, 1));
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
await db.insert(smtpSettings).values({ id: 1, ...values, password: req.body.vault ? '' : (req.body.password ?? '') });
|
|
279
|
+
}
|
|
280
|
+
const diff = {};
|
|
281
|
+
if (existing) {
|
|
282
|
+
const before = { host: existing.host, port: existing.port, secure: existing.secure, user: existing.user, fromAddress: existing.fromAddress, fromName: existing.fromName };
|
|
283
|
+
const after = { host: req.body.host, port: req.body.port, secure: req.body.secure, user: req.body.user ?? '', fromAddress: req.body.fromAddress, fromName: req.body.fromName };
|
|
284
|
+
Object.assign(diff, audit.diffObjects(before, after));
|
|
285
|
+
}
|
|
286
|
+
if (req.body.password && req.body.password !== SMTP_PASSWORD_MASK)
|
|
287
|
+
diff['password'] = { from: '[redacted]', to: '[redacted]' };
|
|
288
|
+
audit.write(actor(req), existing ? 'update' : 'create', 'smtp_settings', 1, 'SMTP Settings', Object.keys(diff).length ? diff : undefined);
|
|
289
|
+
return { ok: true };
|
|
290
|
+
});
|
|
291
|
+
app.post('/smtp/test', async (req, reply) => {
|
|
292
|
+
const { to } = req.body;
|
|
293
|
+
if (!to)
|
|
294
|
+
return reply.code(400).send({ error: 'Recipient address required' });
|
|
295
|
+
const result = await options.testSmtp(to);
|
|
296
|
+
if (!result.ok)
|
|
297
|
+
return reply.code(422).send({ error: result.error });
|
|
298
|
+
return { ok: true };
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAc,MAAM,qBAAqB,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAgC,MAAM,yBAAyB,CAAA;AAEpG,OAAO,EAAE,SAAS,EAAsB,MAAM,qBAAqB,CAAA;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAoDrE,MAAM,kBAAkB,GAAG,UAAU,CAAA;AAErC,MAAM,cAAc,GAAG,CAAC,KAAc,EAAoC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAE1I;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA4B;IAC7D,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAChE,MAAM,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,4BAA4B,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAA;IACnH,MAAM,MAAM,GAAG,iBAAiB,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IAC1E,MAAM,gBAAgB,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAwB,CAAA,CAAC,yDAAyD;IAC5K,MAAM,oBAAoB,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAA;IAIlG,oHAAoH;IACpH,SAAS,YAAY,CAAmB,GAAM;QAC5C,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YAClE,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK;SACzF,CAAC,CAAC,CAAA;IACL,CAAC;IAED;;;;OAIG;IACH,SAAS,YAAY,CAAC,CAAa;QACjC,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAA;IAC3G,CAAC;IAED,8FAA8F;IAC9F,SAAS,iBAAiB,CAAC,CAAa;QACtC,MAAM,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;QACzC,OAAO;YACL,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK;YACtI,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,SAAS,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,MAAM,CAAC,CAAC,CAAC,KAAK;YACzG,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC,YAAY,MAAM,CAAC,CAAC,QAAQ,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,KAAK;SACpI,CAAA;IACH,CAAC;IAED,MAAM,eAAe,GAAG,KAAK,EAAE,GAAW,EAAE,EAAE,CAC5C,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,oBAAoB,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAE/H,OAAO,KAAK,UAAU,YAAY,CAAC,GAAoB;QACrD,MAAM,KAAK,GAAG,CAAC,GAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;QAEjE,GAAG,CAAC,GAAG,CAAsD,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACxF,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAA;YAC/F,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAA;YACvB,MAAM,UAAU,GAAG,EAAE,CAAA;YACrB,IAAI,KAAK,CAAC,QAAQ,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;YACxF,IAAI,KAAK,CAAC,WAAW,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;YACzG,IAAI,KAAK,CAAC,aAAa,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,WAAW,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAA;YACvG,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;YAC5G,IAAI,KAAK,CAAC,WAAW,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;YACjG,IAAI,KAAK,CAAC,MAAM,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,sBAAsB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;YAChG,IAAI,KAAK,CAAC,IAAI,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,sBAAsB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;YAC5F,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAA;YACvH,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YAChE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACtC,EAAE,CAAC,MAAM,CAAC;oBACR,EAAE,EAAE,sBAAsB,CAAC,EAAE,EAAE,SAAS,EAAE,sBAAsB,CAAC,SAAS;oBAC1E,WAAW,EAAE,sBAAsB,CAAC,WAAW,EAAE,WAAW,EAAE,sBAAsB,CAAC,WAAW;oBAChG,QAAQ,EAAE,sBAAsB,CAAC,QAAQ,EAAE,UAAU,EAAE,sBAAsB,CAAC,UAAU;oBACxF,SAAS,EAAE,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,sBAAsB,CAAC,MAAM;oBAClF,YAAY,EAAE,sBAAsB,CAAC,YAAY,EAAE,cAAc,EAAE,sBAAsB,CAAC,cAAc;oBACxG,YAAY,EAAE,sBAAsB,CAAC,YAAY,EAAE,WAAW,EAAE,sBAAsB,CAAC,WAAW;oBAClG,aAAa,EAAE,sBAAsB,CAAC,aAAa,EAAE,aAAa,EAAE,sBAAsB,CAAC,aAAa;oBACxG,WAAW,EAAE,sBAAsB,CAAC,WAAW,EAAE,SAAS,EAAE,sBAAsB,CAAC,SAAS;oBAC5F,iBAAiB,EAAE,sBAAsB,CAAC,iBAAiB,EAAE,QAAQ,EAAE,sBAAsB,CAAC,QAAQ;oBACtG,SAAS,EAAE,sBAAsB,CAAC,SAAS,EAAE,SAAS,EAAE,sBAAsB,CAAC,SAAS;iBACzF,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;gBACxH,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,CAAQ,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;aACtF,CAAC,CAAA;YACF,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAA;YAClC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,EAAE,CAAA;QACpG,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,GAAG,CAA6B,iBAAiB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YAC1E,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAChC,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC7G,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAA;YACzI,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,4BAA4B,CAAC;iBAClE,KAAK,CAAC,EAAE,CAAC,4BAA4B,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,4BAA4B,CAAC,aAAa,CAAC,CAAC,CAAA;YACnH,OAAO,EAAE,GAAG,YAAY,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;QACtE,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,IAAI,CAA6B,uBAAuB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YACjF,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAChC,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC7G,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAA;YACzI,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAC,CAAA;YACjH,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;YAC/B,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,uBAAuB,EAAE,EAAE,EAAE,GAAG,QAAQ,CAAC,WAAW,MAAM,QAAQ,CAAC,UAAU,EAAE,EAAE;gBACvH,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE;aACvC,CAAC,CAAA;YACF,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACxG,sGAAsG;YACtG,OAAO,GAAG,IAAI,YAAY,CAAC,GAAG,CAAC,CAAA;QACjC,CAAC,CAAC,CAAA;QAEF,0EAA0E;QAE1E,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YACzD,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAC/B,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,IAAI,CAGH,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YACrC,IAAI,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAA;YAC3H,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAA+B,CAAC,EAAE,CAAC;gBACtE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;YAC5F,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClG,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAA;YACpE,CAAC;YACD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,SAAS,CAAC,CAAA;YAClE,IAAI,OAAO,IAAI,QAAQ;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;YAC/E,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;YACzF,IAAI,YAAY;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;YACtE,MAAM,aAAa,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;YACxE,IAAI,aAAa;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAA;YACxE,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;YAC1F,IAAI,OAAO,IAAI,WAAW;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,CAAC,CAAA;YACpG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACtB,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,MAAM,CAAC;gBAC3D,GAAG,EAAE,WAAW,CAAC,GAAG;gBACpB,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI;gBACnB,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI;gBACnB,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACvC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC;gBAC9B,gBAAgB,EAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC;gBAChD,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACvE,SAAS,EAAE,GAAG;gBACd,SAAS,EAAE,GAAG;aACf,CAAC,CAAC,SAAS,EAAE,CAAA;YACd,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAA;YACrB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EACpE,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC,gBAAgB,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;YACpI,OAAO,YAAY,CAAC,CAAC,CAAC,CAAA;QACxB,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,GAAG,CAA6B,eAAe,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YACxE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACrH,IAAI,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YAC3D,OAAO,YAAY,CAAC,CAAC,CAAC,CAAA;QACxB,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,KAAK,CAEH,eAAe,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YAC1C,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAChC,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACzG,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YAElE,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAA+B,CAAC,EAAE,CAAC;gBACrG,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;YAC5F,CAAC;YACD,4GAA4G;YAC5G,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBACpG,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0DAA0D,EAAE,CAAC,CAAA;YACpG,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAA;YACpE,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iEAAiE,EAAE,CAAC,CAAA;YACzI,MAAM,aAAa,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAA;YACpD,IAAI,MAAe,CAAA;YACnB,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAClC,MAAM,MAAM,GAAG,aAAa,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;gBACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBACzD,IAAI,OAAO,IAAI,QAAQ;oBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;gBAC/E,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBACtF,IAAI,YAAY;oBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;gBACtE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;YAC1B,CAAC;YACD,IAAI,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACxD,MAAM,aAAa,GAAG,kBAAkB,CAAC,aAAa,EAAE,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;gBAC9F,IAAI,aAAa;oBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAA;YAC1E,CAAC;YAED,MAAM,OAAO,GAAsD,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;YAC5F,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS;gBAAe,OAAO,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAA;YAC1E,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS;gBAAe,OAAO,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAA;YAC1E,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS;gBAAa,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;YACrF,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS;gBAAY,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAA;YAChF,IAAI,GAAG,CAAC,IAAI,CAAC,gBAAgB,KAAK,SAAS;gBAAG,OAAO,CAAC,gBAAgB,GAAG,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAA;YAClG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS;gBAAQ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAA;YAE9H,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;YACrH,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAA;YACrB,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,EAAE,GAAG,iBAAiB,CAAC,QAAQ,CAAC,EAA6B,CAAA;YAC9L,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC,gBAAgB,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAA6B,CAAA;YAC1J,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;YAC7C,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAA;YACzG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM;gBAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAChH,OAAO,YAAY,CAAC,CAAC,CAAC,CAAA;QACxB,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,MAAM,CAA6B,eAAe,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YAC3E,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAChC,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACzG,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;YAClC,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;YAC5E,IAAI,QAAQ,EAAE,CAAC;gBACb,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,EACzE,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;YACjE,CAAC;YACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;QAC/B,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,IAAI,CAA6B,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YAC9E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;YAC/D,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;YACpE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QACrB,CAAC,CAAC,CAAA;QAEF,0EAA0E;QAE1E,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;YAC1B,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACrD,IAAI,CAAC,GAAG;gBAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;YAC1J,OAAO;gBACL,GAAG,GAAG;gBACN,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE;gBAChD,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;aAC5D,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,GAAG,CAIF,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACtB,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAE1D,8GAA8G;YAC9G,mEAAmE;YACnE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;YACpC,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,CAAC;gBAC3E,MAAM,KAAK,GAAG,QAAQ,EAAE,WAAW,IAAI,IAAI,CAAA;gBAC3C,MAAM,aAAa,GAAG,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAClG,MAAM,UAAU,GAAG,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAA;gBAC3G,MAAM,aAAa,GAAG,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAA;gBAClI,IAAI,CAAC,aAAa,IAAI,CAAC,UAAU,IAAI,CAAC,aAAa;oBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;YAC/G,CAAC;YAED,MAAM,gBAAgB,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,kBAAkB,CAAA;YACxF,6FAA6F;YAC7F,2BAA2B;YAC3B,IAAI,QAAQ,EAAE,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,gBAAgB,IAAI,CAChE,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CACtH,EAAE,CAAC;gBACF,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qEAAqE,EAAE,CAAC,CAAA;YAC/G,CAAC;YAED,MAAM,MAAM,GAAG;gBACb,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI;gBACnB,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI;gBACnB,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM;gBACvB,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;gBACjD,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,WAAW;gBACjC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ;gBAC3B,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;gBACnE,SAAS,EAAE,GAAG;aACf,CAAA;YAED,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK;oBAC7B,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;gBAC/D,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;YAC1F,CAAC;iBAAM,CAAC;gBACN,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;YACvH,CAAC;YAED,MAAM,IAAI,GAA4B,EAAE,CAAA;YACxC,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAA6B,CAAA;gBACpM,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAA6B,CAAA;gBACzM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;YACvD,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,kBAAkB;gBAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,CAAA;YAC9H,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;YACzI,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QACrB,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,IAAI,CAA2B,YAAY,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YACpE,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,IAAI,CAAA;YACvB,IAAI,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC,CAAA;YAC7E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;YACzC,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;YACpE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QACrB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;AACH,CAAC"}
|