@meyicloud/meyi-insight-server 1.0.3 → 1.0.5

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.
Files changed (78) hide show
  1. package/dist/controllers/source.controller.d.ts +2 -1
  2. package/dist/controllers/source.controller.d.ts.map +1 -1
  3. package/dist/controllers/source.controller.js +24 -5
  4. package/dist/controllers/source.controller.js.map +1 -1
  5. package/dist/database/schema.d.ts +160 -0
  6. package/dist/database/schema.d.ts.map +1 -1
  7. package/dist/database/schema.js +17 -0
  8. package/dist/database/schema.js.map +1 -1
  9. package/dist/features/sla/domain/sla.evaluator.d.ts +8 -0
  10. package/dist/features/sla/domain/sla.evaluator.d.ts.map +1 -0
  11. package/dist/features/sla/domain/sla.evaluator.js +72 -0
  12. package/dist/features/sla/domain/sla.evaluator.js.map +1 -0
  13. package/dist/features/sla/domain/sla.resolver.d.ts +16 -0
  14. package/dist/features/sla/domain/sla.resolver.d.ts.map +1 -0
  15. package/dist/features/sla/domain/sla.resolver.js +28 -0
  16. package/dist/features/sla/domain/sla.resolver.js.map +1 -0
  17. package/dist/features/sla/domain/sla.types.d.ts +59 -0
  18. package/dist/features/sla/domain/sla.types.d.ts.map +1 -0
  19. package/dist/features/sla/domain/sla.types.js +6 -0
  20. package/dist/features/sla/domain/sla.types.js.map +1 -0
  21. package/dist/features/sla/index.d.ts +7 -0
  22. package/dist/features/sla/index.d.ts.map +1 -0
  23. package/dist/features/sla/index.js +7 -0
  24. package/dist/features/sla/index.js.map +1 -0
  25. package/dist/features/sla/sla-policy.model.d.ts +19 -0
  26. package/dist/features/sla/sla-policy.model.d.ts.map +1 -0
  27. package/dist/features/sla/sla-policy.model.js +44 -0
  28. package/dist/features/sla/sla-policy.model.js.map +1 -0
  29. package/dist/features/sla/sla-policy.service.d.ts +29 -0
  30. package/dist/features/sla/sla-policy.service.d.ts.map +1 -0
  31. package/dist/features/sla/sla-policy.service.js +67 -0
  32. package/dist/features/sla/sla-policy.service.js.map +1 -0
  33. package/dist/features/sla/sla.controller.d.ts +9 -0
  34. package/dist/features/sla/sla.controller.d.ts.map +1 -0
  35. package/dist/features/sla/sla.controller.js +52 -0
  36. package/dist/features/sla/sla.controller.js.map +1 -0
  37. package/dist/features/sla/sla.routes.d.ts +4 -0
  38. package/dist/features/sla/sla.routes.d.ts.map +1 -0
  39. package/dist/features/sla/sla.routes.js +12 -0
  40. package/dist/features/sla/sla.routes.js.map +1 -0
  41. package/dist/features/sla/sla.schema.d.ts +8 -0
  42. package/dist/features/sla/sla.schema.d.ts.map +1 -0
  43. package/dist/features/sla/sla.schema.js +18 -0
  44. package/dist/features/sla/sla.schema.js.map +1 -0
  45. package/dist/features/sources/cloudtrail/controller.d.ts.map +1 -1
  46. package/dist/features/sources/cloudtrail/controller.js +35 -14
  47. package/dist/features/sources/cloudtrail/controller.js.map +1 -1
  48. package/dist/features/sources/cloudtrail/reconciler.d.ts +8 -0
  49. package/dist/features/sources/cloudtrail/reconciler.d.ts.map +1 -0
  50. package/dist/features/sources/cloudtrail/reconciler.js +111 -0
  51. package/dist/features/sources/cloudtrail/reconciler.js.map +1 -0
  52. package/dist/features/sources/cloudtrail/template.d.ts.map +1 -1
  53. package/dist/features/sources/cloudtrail/template.js +29 -5
  54. package/dist/features/sources/cloudtrail/template.js.map +1 -1
  55. package/dist/migrations/run.d.ts.map +1 -1
  56. package/dist/migrations/run.js +26 -0
  57. package/dist/migrations/run.js.map +1 -1
  58. package/dist/models/incident.model.d.ts.map +1 -1
  59. package/dist/models/incident.model.js +1 -0
  60. package/dist/models/incident.model.js.map +1 -1
  61. package/dist/routes/index.d.ts +2 -0
  62. package/dist/routes/index.d.ts.map +1 -1
  63. package/dist/routes/index.js +4 -0
  64. package/dist/routes/index.js.map +1 -1
  65. package/dist/routes/source.routes.js +1 -1
  66. package/dist/routes/source.routes.js.map +1 -1
  67. package/dist/server.d.ts.map +1 -1
  68. package/dist/server.js +1 -0
  69. package/dist/server.js.map +1 -1
  70. package/dist/service.d.ts +7 -4
  71. package/dist/service.d.ts.map +1 -1
  72. package/dist/service.js +7 -3
  73. package/dist/service.js.map +1 -1
  74. package/dist/services/incident.service.d.ts +7 -4
  75. package/dist/services/incident.service.d.ts.map +1 -1
  76. package/dist/services/incident.service.js +15 -19
  77. package/dist/services/incident.service.js.map +1 -1
  78. package/package.json +2 -1
@@ -0,0 +1,16 @@
1
+ import type { SlaPolicy, SlaPolicyRecord, SlaPolicyResolver } from "./sla.types.js";
2
+ /** Fallback limits when an account has no explicit policy (matches historic behaviour). */
3
+ export declare const DEFAULT_SLA_CRITICAL: {
4
+ readonly ackMinutes: 10;
5
+ readonly resolveMinutes: 60;
6
+ };
7
+ export declare const DEFAULT_SLA_STANDARD: {
8
+ readonly ackMinutes: 30;
9
+ readonly resolveMinutes: 120;
10
+ };
11
+ export declare function severityOf(tags?: Record<string, string> | null): string;
12
+ export declare function defaultSlaPolicy(tags?: Record<string, string> | null): SlaPolicy;
13
+ export declare function policyFromRecord(record: Pick<SlaPolicyRecord, "enabled" | "ackMinutes" | "resolveMinutes">): SlaPolicy;
14
+ /** Builds a resolver over a set of account overrides. An account policy applies to every severity. */
15
+ export declare function createSlaResolver(records: SlaPolicyRecord[]): SlaPolicyResolver;
16
+ //# sourceMappingURL=sla.resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.resolver.d.ts","sourceRoot":"","sources":["../../../../src/features/sla/domain/sla.resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEpF,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB;;;CAAkD,CAAC;AACpF,eAAO,MAAM,oBAAoB;;;CAAmD,CAAC;AAErF,wBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,MAAM,CAEvE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,CAIhF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,YAAY,GAAG,gBAAgB,CAAC,GAAG,SAAS,CAOtH;AAED,sGAAsG;AACtG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAM/E"}
@@ -0,0 +1,28 @@
1
+ /** Fallback limits when an account has no explicit policy (matches historic behaviour). */
2
+ export const DEFAULT_SLA_CRITICAL = { ackMinutes: 10, resolveMinutes: 60 };
3
+ export const DEFAULT_SLA_STANDARD = { ackMinutes: 30, resolveMinutes: 120 };
4
+ export function severityOf(tags) {
5
+ return String(tags?.severity || tags?.priority || "").toLowerCase();
6
+ }
7
+ export function defaultSlaPolicy(tags) {
8
+ const s = severityOf(tags);
9
+ const limits = s === "critical" || s === "p1" ? DEFAULT_SLA_CRITICAL : DEFAULT_SLA_STANDARD;
10
+ return { enabled: true, ...limits, source: "default" };
11
+ }
12
+ export function policyFromRecord(record) {
13
+ return {
14
+ enabled: record.enabled,
15
+ ackMinutes: record.ackMinutes,
16
+ resolveMinutes: record.resolveMinutes,
17
+ source: "account",
18
+ };
19
+ }
20
+ /** Builds a resolver over a set of account overrides. An account policy applies to every severity. */
21
+ export function createSlaResolver(records) {
22
+ const byAccount = new Map(records.map((r) => [r.accountId, r]));
23
+ return (accountId, tags) => {
24
+ const record = accountId ? byAccount.get(accountId) : undefined;
25
+ return record ? policyFromRecord(record) : defaultSlaPolicy(tags);
26
+ };
27
+ }
28
+ //# sourceMappingURL=sla.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.resolver.js","sourceRoot":"","sources":["../../../../src/features/sla/domain/sla.resolver.ts"],"names":[],"mappings":"AAEA,2FAA2F;AAC3F,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAW,CAAC;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,cAAc,EAAE,GAAG,EAAW,CAAC;AAErF,MAAM,UAAU,UAAU,CAAC,IAAoC;IAC7D,OAAO,MAAM,CAAC,IAAI,EAAE,QAAQ,IAAI,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAoC;IACnE,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,oBAAoB,CAAC;IAC5F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAA0E;IACzG,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,MAAM,EAAE,SAAS;KAClB,CAAC;AACJ,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,iBAAiB,CAAC,OAA0B;IAC1D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE;QACzB,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChE,OAAO,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,59 @@
1
+ /** Where an effective SLA policy came from. */
2
+ export type SlaPolicySource = "account" | "default";
3
+ export interface SlaPolicy {
4
+ enabled: boolean;
5
+ ackMinutes: number;
6
+ resolveMinutes: number;
7
+ source: SlaPolicySource;
8
+ }
9
+ /** Persisted per-account override (one row per tenant + AWS account). */
10
+ export interface SlaPolicyRecord {
11
+ tenantId: string;
12
+ accountId: string;
13
+ enabled: boolean;
14
+ ackMinutes: number;
15
+ resolveMinutes: number;
16
+ updatedBy: string | null;
17
+ updatedAt: Date | string | null;
18
+ }
19
+ export type SlaPhaseState = "met" | "met_late" | "pending" | "overdue" | "disabled";
20
+ export interface SlaPhase {
21
+ state: SlaPhaseState;
22
+ limitMinutes: number;
23
+ /** Minutes from creation until the phase completed (or until now while pending). */
24
+ elapsedMinutes: number | null;
25
+ /** Minutes left while pending; negative once overdue. Null when not pending. */
26
+ remainingMinutes: number | null;
27
+ }
28
+ export interface SlaEvaluation {
29
+ policy: SlaPolicy;
30
+ ack: SlaPhase;
31
+ resolve: SlaPhase;
32
+ /** True when either phase is overdue or was completed late. */
33
+ breached: boolean;
34
+ }
35
+ /** Minimal incident shape the evaluator needs. */
36
+ export interface SlaIncidentInput {
37
+ status: string;
38
+ createdAt: Date | string | null | undefined;
39
+ /** When the incident left "open" (acknowledged). */
40
+ ackedAt?: Date | string | null;
41
+ resolvedAt?: Date | string | null;
42
+ accountId?: string | null;
43
+ tags?: Record<string, string> | null;
44
+ }
45
+ /** Resolves the effective policy for an incident's account. Built once per request. */
46
+ export type SlaPolicyResolver = (accountId: string | null | undefined, tags?: Record<string, string> | null) => SlaPolicy;
47
+ /**
48
+ * Port used by incident code so it doesn't depend on persistence details.
49
+ * `tenantId` undefined yields a resolver that only returns defaults.
50
+ */
51
+ export interface SlaPolicyProvider {
52
+ loadResolver(tenantId?: string): Promise<SlaPolicyResolver>;
53
+ }
54
+ export declare const SLA_LIMITS: {
55
+ readonly minMinutes: 1;
56
+ /** 30 days. */
57
+ readonly maxMinutes: 43200;
58
+ };
59
+ //# sourceMappingURL=sla.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.types.d.ts","sourceRoot":"","sources":["../../../../src/features/sla/domain/sla.types.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAEpF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,QAAQ,CAAC;IACd,OAAO,EAAE,QAAQ,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC5C,oDAAoD;IACpD,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACtC;AAED,uFAAuF;AACvF,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAAK,SAAS,CAAC;AAE1H;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC7D;AAED,eAAO,MAAM,UAAU;;IAErB,eAAe;;CAEP,CAAC"}
@@ -0,0 +1,6 @@
1
+ export const SLA_LIMITS = {
2
+ minMinutes: 1,
3
+ /** 30 days. */
4
+ maxMinutes: 43_200,
5
+ };
6
+ //# sourceMappingURL=sla.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.types.js","sourceRoot":"","sources":["../../../../src/features/sla/domain/sla.types.ts"],"names":[],"mappings":"AA8DA,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,UAAU,EAAE,CAAC;IACb,eAAe;IACf,UAAU,EAAE,MAAM;CACV,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from "./domain/sla.types.js";
2
+ export * from "./domain/sla.resolver.js";
3
+ export * from "./domain/sla.evaluator.js";
4
+ export { SlaPolicyModel } from "./sla-policy.model.js";
5
+ export { SlaPolicyService, SlaError, type AccountSlaPolicy } from "./sla-policy.service.js";
6
+ export { createSlaRouter } from "./sla.routes.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/features/sla/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from "./domain/sla.types.js";
2
+ export * from "./domain/sla.resolver.js";
3
+ export * from "./domain/sla.evaluator.js";
4
+ export { SlaPolicyModel } from "./sla-policy.model.js";
5
+ export { SlaPolicyService, SlaError } from "./sla-policy.service.js";
6
+ export { createSlaRouter } from "./sla.routes.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/features/sla/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAyB,MAAM,yBAAyB,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { SlaPolicyRecord } from "./domain/sla.types.js";
2
+ /** Persistence only: every query is scoped by tenant_id. No business rules here. */
3
+ export declare class SlaPolicyModel {
4
+ private db;
5
+ private tbl;
6
+ constructor(db: any, tbl: any);
7
+ listByTenant(tenantId: string): Promise<SlaPolicyRecord[]>;
8
+ findByAccount(tenantId: string, accountId: string): Promise<SlaPolicyRecord | null>;
9
+ upsert(input: {
10
+ tenantId: string;
11
+ accountId: string;
12
+ enabled: boolean;
13
+ ackMinutes: number;
14
+ resolveMinutes: number;
15
+ updatedBy: string | null;
16
+ }): Promise<SlaPolicyRecord>;
17
+ delete(tenantId: string, accountId: string): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=sla-policy.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla-policy.model.d.ts","sourceRoot":"","sources":["../../../src/features/sla/sla-policy.model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,oFAAoF;AACpF,qBAAa,cAAc;IAEvB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,GAAG;gBADH,EAAE,EAAE,GAAG,EACP,GAAG,EAAE,GAAG;IAGZ,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAI1D,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IASnF,MAAM,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,GAAG,OAAO,CAAC,eAAe,CAAC;IAkBtB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAKjE"}
@@ -0,0 +1,44 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ /** Persistence only: every query is scoped by tenant_id. No business rules here. */
3
+ export class SlaPolicyModel {
4
+ db;
5
+ tbl;
6
+ constructor(db, tbl) {
7
+ this.db = db;
8
+ this.tbl = tbl;
9
+ }
10
+ async listByTenant(tenantId) {
11
+ return this.db.select().from(this.tbl).where(eq(this.tbl.tenantId, tenantId));
12
+ }
13
+ async findByAccount(tenantId, accountId) {
14
+ const [row] = await this.db
15
+ .select()
16
+ .from(this.tbl)
17
+ .where(and(eq(this.tbl.tenantId, tenantId), eq(this.tbl.accountId, accountId)))
18
+ .limit(1);
19
+ return row ?? null;
20
+ }
21
+ async upsert(input) {
22
+ const [row] = await this.db
23
+ .insert(this.tbl)
24
+ .values(input)
25
+ .onConflictDoUpdate({
26
+ target: [this.tbl.tenantId, this.tbl.accountId],
27
+ set: {
28
+ enabled: input.enabled,
29
+ ackMinutes: input.ackMinutes,
30
+ resolveMinutes: input.resolveMinutes,
31
+ updatedBy: input.updatedBy,
32
+ updatedAt: new Date(),
33
+ },
34
+ })
35
+ .returning();
36
+ return row;
37
+ }
38
+ async delete(tenantId, accountId) {
39
+ await this.db
40
+ .delete(this.tbl)
41
+ .where(and(eq(this.tbl.tenantId, tenantId), eq(this.tbl.accountId, accountId)));
42
+ }
43
+ }
44
+ //# sourceMappingURL=sla-policy.model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla-policy.model.js","sourceRoot":"","sources":["../../../src/features/sla/sla-policy.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAGtC,oFAAoF;AACpF,MAAM,OAAO,cAAc;IAEf;IACA;IAFV,YACU,EAAO,EACP,GAAQ;QADR,OAAE,GAAF,EAAE,CAAK;QACP,QAAG,GAAH,GAAG,CAAK;IACf,CAAC;IAEJ,KAAK,CAAC,YAAY,CAAC,QAAgB;QACjC,OAAO,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAAgB,EAAE,SAAiB;QACrD,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE;aACxB,MAAM,EAAE;aACR,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;aACd,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;aAC9E,KAAK,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO,GAAG,IAAI,IAAI,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAOZ;QACC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE;aACxB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;aAChB,MAAM,CAAC,KAAK,CAAC;aACb,kBAAkB,CAAC;YAClB,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAC/C,GAAG,EAAE;gBACH,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,cAAc,EAAE,KAAK,CAAC,cAAc;gBACpC,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,SAAS,EAAE,IAAI,IAAI,EAAE;aACtB;SACF,CAAC;aACD,SAAS,EAAE,CAAC;QACf,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,SAAiB;QAC9C,MAAM,IAAI,CAAC,EAAE;aACV,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;aAChB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import type { ConnectorService } from "../../services/connector.service.js";
2
+ import type { SlaPolicyModel } from "./sla-policy.model.js";
3
+ import type { SlaPolicy, SlaPolicyProvider, SlaPolicyResolver } from "./domain/sla.types.js";
4
+ export declare class SlaError extends Error {
5
+ readonly status: 400 | 404;
6
+ readonly details?: unknown;
7
+ constructor(status: 400 | 404, message: string, details?: unknown);
8
+ }
9
+ export interface AccountSlaPolicy extends SlaPolicy {
10
+ accountId: string;
11
+ updatedBy: string | null;
12
+ updatedAt: Date | string | null;
13
+ }
14
+ export declare class SlaPolicyService implements SlaPolicyProvider {
15
+ private model;
16
+ private connectors;
17
+ constructor(model: SlaPolicyModel, connectors: ConnectorService);
18
+ /** The account must be one of this tenant's connectors; prevents writing policies for foreign accounts. */
19
+ private assertConnector;
20
+ private toAccountPolicy;
21
+ list(tenantId: string): Promise<AccountSlaPolicy[]>;
22
+ get(tenantId: string, accountId: string): Promise<AccountSlaPolicy>;
23
+ upsert(tenantId: string, accountId: string, input: unknown, actor: string | null): Promise<AccountSlaPolicy>;
24
+ /** Removes the override so the account falls back to defaults. */
25
+ reset(tenantId: string, accountId: string): Promise<AccountSlaPolicy>;
26
+ /** One query per request; used by incident dashboards, analytics and detail. */
27
+ loadResolver(tenantId?: string): Promise<SlaPolicyResolver>;
28
+ }
29
+ //# sourceMappingURL=sla-policy.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla-policy.service.d.ts","sourceRoot":"","sources":["../../../src/features/sla/sla-policy.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAmB,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE9G,qBAAa,QAAS,SAAQ,KAAK;IAE/B,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG;IAE1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO;gBAFjB,MAAM,EAAE,GAAG,GAAG,GAAG,EAC1B,OAAO,EAAE,MAAM,EACN,OAAO,CAAC,EAAE,OAAO;CAI7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,qBAAa,gBAAiB,YAAW,iBAAiB;IAEtD,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,UAAU;gBADV,KAAK,EAAE,cAAc,EACrB,UAAU,EAAE,gBAAgB;IAGtC,2GAA2G;YAC7F,eAAe;IAK7B,OAAO,CAAC,eAAe;IAKjB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAKnD,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAKnE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAWlH,kEAAkE;IAC5D,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAM3E,gFAAgF;IAC1E,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;CASlE"}
@@ -0,0 +1,67 @@
1
+ import { SlaPolicyInputSchema } from "./sla.schema.js";
2
+ import { createSlaResolver, defaultSlaPolicy, policyFromRecord } from "./domain/sla.resolver.js";
3
+ export class SlaError extends Error {
4
+ status;
5
+ details;
6
+ constructor(status, message, details) {
7
+ super(message);
8
+ this.status = status;
9
+ this.details = details;
10
+ }
11
+ }
12
+ export class SlaPolicyService {
13
+ model;
14
+ connectors;
15
+ constructor(model, connectors) {
16
+ this.model = model;
17
+ this.connectors = connectors;
18
+ }
19
+ /** The account must be one of this tenant's connectors; prevents writing policies for foreign accounts. */
20
+ async assertConnector(tenantId, accountId) {
21
+ const connector = await this.connectors.findByAccountId(tenantId, accountId);
22
+ if (!connector)
23
+ throw new SlaError(404, "Connector not found");
24
+ }
25
+ toAccountPolicy(accountId, record) {
26
+ if (!record)
27
+ return { ...defaultSlaPolicy(), accountId, updatedBy: null, updatedAt: null };
28
+ return { ...policyFromRecord(record), accountId, updatedBy: record.updatedBy, updatedAt: record.updatedAt };
29
+ }
30
+ async list(tenantId) {
31
+ const records = await this.model.listByTenant(tenantId);
32
+ return records.map((r) => this.toAccountPolicy(r.accountId, r));
33
+ }
34
+ async get(tenantId, accountId) {
35
+ await this.assertConnector(tenantId, accountId);
36
+ return this.toAccountPolicy(accountId, await this.model.findByAccount(tenantId, accountId));
37
+ }
38
+ async upsert(tenantId, accountId, input, actor) {
39
+ const parsed = SlaPolicyInputSchema.safeParse(input);
40
+ if (!parsed.success) {
41
+ throw new SlaError(400, parsed.error.issues[0]?.message ?? "Invalid SLA policy", parsed.error.issues);
42
+ }
43
+ await this.assertConnector(tenantId, accountId);
44
+ const data = parsed.data;
45
+ const record = await this.model.upsert({ tenantId, accountId, ...data, updatedBy: actor });
46
+ return this.toAccountPolicy(accountId, record);
47
+ }
48
+ /** Removes the override so the account falls back to defaults. */
49
+ async reset(tenantId, accountId) {
50
+ await this.assertConnector(tenantId, accountId);
51
+ await this.model.delete(tenantId, accountId);
52
+ return this.toAccountPolicy(accountId, null);
53
+ }
54
+ /** One query per request; used by incident dashboards, analytics and detail. */
55
+ async loadResolver(tenantId) {
56
+ if (!tenantId)
57
+ return createSlaResolver([]);
58
+ try {
59
+ return createSlaResolver(await this.model.listByTenant(tenantId));
60
+ }
61
+ catch {
62
+ // Never break incident views because the policy table is unavailable; fall back to defaults.
63
+ return createSlaResolver([]);
64
+ }
65
+ }
66
+ }
67
+ //# sourceMappingURL=sla-policy.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla-policy.service.js","sourceRoot":"","sources":["../../../src/features/sla/sla-policy.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Request, Response } from "express";
2
+ import { type SlaPolicyService } from "./sla-policy.service.js";
3
+ export declare function createSlaController(service: SlaPolicyService): {
4
+ list(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
5
+ get(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
6
+ upsert(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
7
+ reset(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
8
+ };
9
+ //# sourceMappingURL=sla.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.controller.d.ts","sourceRoot":"","sources":["../../../src/features/sla/sla.controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEjD,OAAO,EAAY,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAe1E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB;cAEzC,OAAO,OAAO,QAAQ;aAOvB,OAAO,OAAO,QAAQ;gBAOnB,OAAO,OAAO,QAAQ;eAQvB,OAAO,OAAO,QAAQ;EAQ1C"}
@@ -0,0 +1,52 @@
1
+ import { resolveTenantId } from "../../tenancy.js";
2
+ import { SlaError } from "./sla-policy.service.js";
3
+ function actorLabel(req) {
4
+ const u = req.user;
5
+ if (!u)
6
+ return null;
7
+ return u.email || u.full_name || u.fullName || u.name || u.id || null;
8
+ }
9
+ function fail(res, e) {
10
+ if (e instanceof SlaError) {
11
+ return res.status(e.status).json({ ok: false, error: e.message, details: e.details });
12
+ }
13
+ return res.status(500).json({ ok: false, error: e?.message ?? "Internal error" });
14
+ }
15
+ export function createSlaController(service) {
16
+ return {
17
+ async list(req, res) {
18
+ try {
19
+ return res.json({ ok: true, policies: await service.list(resolveTenantId(req)) });
20
+ }
21
+ catch (e) {
22
+ return fail(res, e);
23
+ }
24
+ },
25
+ async get(req, res) {
26
+ try {
27
+ return res.json({ ok: true, policy: await service.get(resolveTenantId(req), req.params.accountId) });
28
+ }
29
+ catch (e) {
30
+ return fail(res, e);
31
+ }
32
+ },
33
+ async upsert(req, res) {
34
+ try {
35
+ const policy = await service.upsert(resolveTenantId(req), req.params.accountId, req.body, actorLabel(req));
36
+ return res.json({ ok: true, policy });
37
+ }
38
+ catch (e) {
39
+ return fail(res, e);
40
+ }
41
+ },
42
+ async reset(req, res) {
43
+ try {
44
+ return res.json({ ok: true, policy: await service.reset(resolveTenantId(req), req.params.accountId) });
45
+ }
46
+ catch (e) {
47
+ return fail(res, e);
48
+ }
49
+ },
50
+ };
51
+ }
52
+ //# sourceMappingURL=sla.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.controller.js","sourceRoot":"","sources":["../../../src/features/sla/sla.controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { Router } from "express";
2
+ import type { SlaPolicyService } from "./sla-policy.service.js";
3
+ export declare function createSlaRouter(service: SlaPolicyService): Router;
4
+ //# sourceMappingURL=sla.routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.routes.d.ts","sourceRoot":"","sources":["../../../src/features/sla/sla.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAUjE"}
@@ -0,0 +1,12 @@
1
+ import { Router } from "express";
2
+ import { createSlaController } from "./sla.controller.js";
3
+ export function createSlaRouter(service) {
4
+ const router = Router();
5
+ const ctrl = createSlaController(service);
6
+ router.get("/policies", ctrl.list.bind(ctrl));
7
+ router.get("/policies/:accountId", ctrl.get.bind(ctrl));
8
+ router.put("/policies/:accountId", ctrl.upsert.bind(ctrl));
9
+ router.delete("/policies/:accountId", ctrl.reset.bind(ctrl));
10
+ return router;
11
+ }
12
+ //# sourceMappingURL=sla.routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.routes.js","sourceRoot":"","sources":["../../../src/features/sla/sla.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1D,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE1C,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7D,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ export declare const SlaPolicyInputSchema: z.ZodObject<{
3
+ enabled: z.ZodBoolean;
4
+ ackMinutes: z.ZodNumber;
5
+ resolveMinutes: z.ZodNumber;
6
+ }, z.core.$strip>;
7
+ export type SlaPolicyInput = z.infer<typeof SlaPolicyInputSchema>;
8
+ //# sourceMappingURL=sla.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.schema.d.ts","sourceRoot":"","sources":["../../../src/features/sla/sla.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,eAAO,MAAM,oBAAoB;;;;iBAS7B,CAAC;AAEL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ import { SLA_LIMITS } from "./domain/sla.types.js";
3
+ const minutes = z
4
+ .number()
5
+ .int("Must be a whole number of minutes")
6
+ .min(SLA_LIMITS.minMinutes, `Must be at least ${SLA_LIMITS.minMinutes} minute`)
7
+ .max(SLA_LIMITS.maxMinutes, `Must be at most ${SLA_LIMITS.maxMinutes} minutes (30 days)`);
8
+ export const SlaPolicyInputSchema = z
9
+ .object({
10
+ enabled: z.boolean(),
11
+ ackMinutes: minutes,
12
+ resolveMinutes: minutes,
13
+ })
14
+ .refine((v) => v.resolveMinutes >= v.ackMinutes, {
15
+ message: "Resolve limit must be greater than or equal to the acknowledge limit",
16
+ path: ["resolveMinutes"],
17
+ });
18
+ //# sourceMappingURL=sla.schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sla.schema.js","sourceRoot":"","sources":["../../../src/features/sla/sla.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,MAAM,OAAO,GAAG,CAAC;KACd,MAAM,EAAE;KACR,GAAG,CAAC,mCAAmC,CAAC;KACxC,GAAG,CAAC,UAAU,CAAC,UAAU,EAAE,oBAAoB,UAAU,CAAC,UAAU,SAAS,CAAC;KAC9E,GAAG,CAAC,UAAU,CAAC,UAAU,EAAE,mBAAmB,UAAU,CAAC,UAAU,oBAAoB,CAAC,CAAC;AAE5F,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,CAAC;IACN,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,UAAU,EAAE,OAAO;IACnB,cAAc,EAAE,OAAO;CACxB,CAAC;KACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,UAAU,EAAE;IAC/C,OAAO,EAAE,sEAAsE;IAC/E,IAAI,EAAE,CAAC,gBAAgB,CAAC;CACzB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/features/sources/cloudtrail/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AA4F/E,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,aAAa,EAC5B,gBAAgB,CAAC,EAAE,gBAAgB;IAGjC;;;OAGG;kBACiB,OAAO,OAAO,QAAQ;IA6B1C;;;;;;;;;OASG;0BACyB,OAAO,OAAO,QAAQ;IA0LlD;;;OAGG;+BAC8B,OAAO,OAAO,QAAQ;IAiCvD;;;OAGG;gCAC+B,OAAO,OAAO,QAAQ;IAsDxD;;;;OAIG;0BACyB,OAAO,OAAO,QAAQ;EAmDrD"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/features/sources/cloudtrail/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AA6F/E,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,aAAa,EAC5B,gBAAgB,CAAC,EAAE,gBAAgB;IAGjC;;;OAGG;kBACiB,OAAO,OAAO,QAAQ;IAkC1C;;;;;;;;;OASG;0BACyB,OAAO,OAAO,QAAQ;IA6LlD;;;OAGG;+BAC8B,OAAO,OAAO,QAAQ;IAiCvD;;;OAGG;gCAC+B,OAAO,OAAO,QAAQ;IAoExD;;;;OAIG;0BACyB,OAAO,OAAO,QAAQ;EAmDrD"}
@@ -1,5 +1,6 @@
1
1
  import { buildCloudTrailCentralStackTemplate, ALL_AWS_ACTIVE_REGIONS } from "./template.js";
2
2
  import { resolveTenantId } from "../../../tenancy.js";
3
+ import { reconcileCloudTrailDeployment } from "./reconciler.js";
3
4
  function normalizeSourceType(value) {
4
5
  const normalized = String(value || "")
5
6
  .toLowerCase()
@@ -80,12 +81,16 @@ export function createCloudTrailSourceController(sourceService, connectorService
80
81
  */
81
82
  async getSetup(req, res) {
82
83
  try {
83
- const source = await sourceService.getById(req.params.id);
84
+ let source = await sourceService.getById(req.params.id);
84
85
  if (!source)
85
86
  return res.status(404).json({ ok: false, error: "Source not found" });
86
87
  if (!isCloudTrailType(source.type)) {
87
88
  return res.status(400).json({ ok: false, error: "Source type must be cloudtrail" });
88
89
  }
90
+ const tenantId = resolveTenantId(req);
91
+ if (String(source.awsDeploymentStatus || "").toLowerCase() === "pending") {
92
+ source = await reconcileCloudTrailDeployment(source, sourceService, connectorService, tenantId);
93
+ }
89
94
  return res.json({
90
95
  ok: true,
91
96
  source: {
@@ -119,13 +124,16 @@ export function createCloudTrailSourceController(sourceService, connectorService
119
124
  */
120
125
  async getConsoleDeploy(req, res) {
121
126
  try {
122
- const source = await sourceService.getById(req.params.id);
127
+ let source = await sourceService.getById(req.params.id);
123
128
  if (!source)
124
129
  return res.status(404).json({ ok: false, error: "Source not found" });
125
130
  if (!isCloudTrailType(source.type)) {
126
131
  return res.status(400).json({ ok: false, error: "Source type must be cloudtrail" });
127
132
  }
128
133
  const tenantId = resolveTenantId(req);
134
+ if (String(source.awsDeploymentStatus || "").toLowerCase() === "pending") {
135
+ source = await reconcileCloudTrailDeployment(source, sourceService, connectorService, tenantId);
136
+ }
129
137
  const region = String(req.query.region || source.awsRegion || "ap-south-1").trim() || "ap-south-1";
130
138
  const stackName = sanitizeAwsName(req.query.stackName || source.awsStackName, `meyi-cloudtrail-${source.id.slice(0, 8)}`, 128);
131
139
  // createNewTrail: query param > stored value > default true
@@ -218,7 +226,7 @@ export function createCloudTrailSourceController(sourceService, connectorService
218
226
  webhookUrl = `${urlObj.toString().replace(/\/$/, "")}${webhookPath}`;
219
227
  }
220
228
  const deploymentStatusPath = `/api/v1/insight/sources/${source.id}/aws/cloudtrail/deployment-status`;
221
- const deploymentStatusUrl = `${baseUrl}${deploymentStatusPath}?token=${encodeURIComponent(source.webhookToken)}`;
229
+ const deploymentStatusUrl = `${baseUrl}${deploymentStatusPath}`;
222
230
  const templatePath = `/api/v1/insight/sources/${source.id}/aws/cloudtrail/central-stack-template.yml`;
223
231
  const templateUrl = `${baseUrl}${templatePath}?token=${encodeURIComponent(source.webhookToken)}`;
224
232
  const templateDownloadUrl = templateUrl;
@@ -324,7 +332,16 @@ export function createCloudTrailSourceController(sourceService, connectorService
324
332
  if (!isCloudTrailType(source.type)) {
325
333
  return res.status(400).json({ ok: false, error: "Source type must be cloudtrail" });
326
334
  }
327
- const token = String(req.query.token || req.body?.token || "").trim();
335
+ let token = String(req.query.token || req.body?.token || "").trim();
336
+ if (!token && req.headers.authorization) {
337
+ const auth = String(req.headers.authorization).trim();
338
+ if (auth.toLowerCase().startsWith("bearer ")) {
339
+ token = auth.slice(7).trim();
340
+ }
341
+ else {
342
+ token = auth;
343
+ }
344
+ }
328
345
  if (!token || token !== String(source.webhookToken || "")) {
329
346
  return res.status(403).json({ ok: false, error: "Invalid deployment status token" });
330
347
  }
@@ -332,27 +349,31 @@ export function createCloudTrailSourceController(sourceService, connectorService
332
349
  if (req.body?.source === "aws.cloudformation" && req.body?.detail) {
333
350
  const detail = req.body.detail;
334
351
  const statusDetails = detail["status-details"] || {};
352
+ const statusVal = statusDetails["detailed-status"] || statusDetails["status"] || detail["status"];
335
353
  parsedBody = {
336
- operationStatus: statusDetails["detailed-status"] || statusDetails["status"],
337
- reason: statusDetails["status-reason"],
338
- stackId: detail["stack-set-arn"] || detail["stack-id"] || req.body.resources?.[0],
339
- stackName: (detail["stack-set-arn"] || detail["stack-id"] || req.body.resources?.[0]) ? (detail["stack-set-arn"] || detail["stack-id"] || req.body.resources?.[0]).split("/")[1] : undefined
354
+ operationStatus: statusVal,
355
+ reason: statusDetails["status-reason"] || detail["status-reason"],
356
+ stackId: detail["stack-id"] || detail["stack-set-arn"] || req.body.resources?.[0],
357
+ stackName: (detail["stack-id"] || detail["stack-set-arn"] || req.body.resources?.[0]) ? (detail["stack-id"] || detail["stack-set-arn"] || req.body.resources?.[0]).split("/")[1] : undefined
340
358
  };
341
359
  }
342
- // Map CloudFormation StackSet status to internal status
360
+ // Map CloudFormation Stack / StackSet / StackInstance status to internal status
343
361
  const rawStatus = String(parsedBody?.operationStatus || parsedBody?.status || "").trim().toLowerCase();
344
362
  let requestedStatus = rawStatus;
345
- if (rawStatus === "succeeded" || rawStatus === "create_complete" || rawStatus === "update_complete" || rawStatus === "deployed")
363
+ if (rawStatus === "succeeded" || rawStatus === "create_complete" || rawStatus === "update_complete" || rawStatus === "deployed" || rawStatus === "current") {
346
364
  requestedStatus = "deployed";
347
- else if (rawStatus.includes("failed"))
365
+ }
366
+ else if (rawStatus.includes("failed") || rawStatus.includes("rollback")) {
348
367
  requestedStatus = "failed";
349
- else if (rawStatus === "delete_complete" || rawStatus === "deleted")
368
+ }
369
+ else if (rawStatus === "delete_complete" || rawStatus === "deleted") {
350
370
  requestedStatus = "deleted";
371
+ }
351
372
  const allowed = new Set(["not_started", "pending", "deployed", "failed", "deleted"]);
352
373
  const status = allowed.has(requestedStatus) ? requestedStatus : "pending";
353
374
  const updated = await sourceService.updateCloudTrailDeployment(source.id, {
354
375
  awsDeploymentStatus: status,
355
- awsDeploymentStatusReason: String(parsedBody?.reason || "").trim() || null,
376
+ awsDeploymentStatusReason: String(parsedBody?.reason || "").trim() || (status === "deployed" ? "Deployment completed in AWS" : null),
356
377
  awsStackId: String(parsedBody?.stackId || "").trim() || null,
357
378
  awsStackName: String(parsedBody?.stackName || source.awsStackName || "").trim() || null,
358
379
  });
@@ -387,7 +408,7 @@ export function createCloudTrailSourceController(sourceService, connectorService
387
408
  }
388
409
  const baseUrl = buildPublicBaseUrl(req);
389
410
  let webhookUrl = `${baseUrl}/api/v1/insight/webhook/${source.webhookToken}`;
390
- const deploymentStatusUrl = `${baseUrl}/api/v1/insight/sources/${source.id}/aws/cloudtrail/deployment-status?token=${encodeURIComponent(source.webhookToken)}`;
411
+ const deploymentStatusUrl = `${baseUrl}/api/v1/insight/sources/${source.id}/aws/cloudtrail/deployment-status`;
391
412
  // Read from query params first (if provided by frontend during download), then fallback to database
392
413
  const reqCreateNewTrail = req.query.createNewTrail !== undefined
393
414
  ? String(req.query.createNewTrail).toLowerCase() !== "false"