@hasna/microservices 0.0.9 → 0.0.11
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/bin/index.js +236 -36
- package/bin/mcp.js +153 -4
- package/dist/index.js +120 -3
- package/microservices/microservice-analytics/package.json +27 -0
- package/microservices/microservice-analytics/src/cli/index.ts +373 -0
- package/microservices/microservice-analytics/src/db/analytics.ts +564 -0
- package/microservices/microservice-analytics/src/db/database.ts +93 -0
- package/microservices/microservice-analytics/src/db/migrations.ts +50 -0
- package/microservices/microservice-analytics/src/index.ts +37 -0
- package/microservices/microservice-analytics/src/mcp/index.ts +334 -0
- package/microservices/microservice-assets/package.json +27 -0
- package/microservices/microservice-assets/src/cli/index.ts +375 -0
- package/microservices/microservice-assets/src/db/assets.ts +370 -0
- package/microservices/microservice-assets/src/db/database.ts +93 -0
- package/microservices/microservice-assets/src/db/migrations.ts +51 -0
- package/microservices/microservice-assets/src/index.ts +32 -0
- package/microservices/microservice-assets/src/mcp/index.ts +346 -0
- package/microservices/microservice-compliance/package.json +27 -0
- package/microservices/microservice-compliance/src/cli/index.ts +467 -0
- package/microservices/microservice-compliance/src/db/compliance.ts +633 -0
- package/microservices/microservice-compliance/src/db/database.ts +93 -0
- package/microservices/microservice-compliance/src/db/migrations.ts +63 -0
- package/microservices/microservice-compliance/src/index.ts +46 -0
- package/microservices/microservice-compliance/src/mcp/index.ts +438 -0
- package/microservices/microservice-habits/package.json +27 -0
- package/microservices/microservice-habits/src/cli/index.ts +315 -0
- package/microservices/microservice-habits/src/db/database.ts +93 -0
- package/microservices/microservice-habits/src/db/habits.ts +451 -0
- package/microservices/microservice-habits/src/db/migrations.ts +46 -0
- package/microservices/microservice-habits/src/index.ts +31 -0
- package/microservices/microservice-habits/src/mcp/index.ts +313 -0
- package/microservices/microservice-health/package.json +27 -0
- package/microservices/microservice-health/src/cli/index.ts +484 -0
- package/microservices/microservice-health/src/db/database.ts +93 -0
- package/microservices/microservice-health/src/db/health.ts +708 -0
- package/microservices/microservice-health/src/db/migrations.ts +70 -0
- package/microservices/microservice-health/src/index.ts +63 -0
- package/microservices/microservice-health/src/mcp/index.ts +437 -0
- package/microservices/microservice-leads/package.json +27 -0
- package/microservices/microservice-leads/src/cli/index.ts +596 -0
- package/microservices/microservice-leads/src/db/database.ts +93 -0
- package/microservices/microservice-leads/src/db/leads.ts +520 -0
- package/microservices/microservice-leads/src/db/lists.ts +151 -0
- package/microservices/microservice-leads/src/db/migrations.ts +93 -0
- package/microservices/microservice-leads/src/index.ts +65 -0
- package/microservices/microservice-leads/src/lib/enrichment.ts +202 -0
- package/microservices/microservice-leads/src/lib/scoring.ts +134 -0
- package/microservices/microservice-leads/src/mcp/index.ts +533 -0
- package/microservices/microservice-notifications/package.json +27 -0
- package/microservices/microservice-notifications/src/cli/index.ts +349 -0
- package/microservices/microservice-notifications/src/db/database.ts +93 -0
- package/microservices/microservice-notifications/src/db/migrations.ts +62 -0
- package/microservices/microservice-notifications/src/db/notifications.ts +509 -0
- package/microservices/microservice-notifications/src/index.ts +41 -0
- package/microservices/microservice-notifications/src/mcp/index.ts +422 -0
- package/microservices/microservice-products/package.json +27 -0
- package/microservices/microservice-products/src/cli/index.ts +416 -0
- package/microservices/microservice-products/src/db/categories.ts +154 -0
- package/microservices/microservice-products/src/db/database.ts +93 -0
- package/microservices/microservice-products/src/db/migrations.ts +58 -0
- package/microservices/microservice-products/src/db/pricing-tiers.ts +66 -0
- package/microservices/microservice-products/src/db/products.ts +452 -0
- package/microservices/microservice-products/src/index.ts +53 -0
- package/microservices/microservice-products/src/mcp/index.ts +453 -0
- package/microservices/microservice-projects/package.json +27 -0
- package/microservices/microservice-projects/src/cli/index.ts +480 -0
- package/microservices/microservice-projects/src/db/database.ts +93 -0
- package/microservices/microservice-projects/src/db/migrations.ts +65 -0
- package/microservices/microservice-projects/src/db/projects.ts +715 -0
- package/microservices/microservice-projects/src/index.ts +57 -0
- package/microservices/microservice-projects/src/mcp/index.ts +501 -0
- package/microservices/microservice-proposals/package.json +27 -0
- package/microservices/microservice-proposals/src/cli/index.ts +400 -0
- package/microservices/microservice-proposals/src/db/database.ts +93 -0
- package/microservices/microservice-proposals/src/db/migrations.ts +52 -0
- package/microservices/microservice-proposals/src/db/proposals.ts +532 -0
- package/microservices/microservice-proposals/src/index.ts +37 -0
- package/microservices/microservice-proposals/src/mcp/index.ts +375 -0
- package/microservices/microservice-reading/package.json +27 -0
- package/microservices/microservice-reading/src/cli/index.ts +464 -0
- package/microservices/microservice-reading/src/db/database.ts +93 -0
- package/microservices/microservice-reading/src/db/migrations.ts +59 -0
- package/microservices/microservice-reading/src/db/reading.ts +524 -0
- package/microservices/microservice-reading/src/index.ts +51 -0
- package/microservices/microservice-reading/src/mcp/index.ts +368 -0
- package/microservices/microservice-travel/package.json +27 -0
- package/microservices/microservice-travel/src/cli/index.ts +505 -0
- package/microservices/microservice-travel/src/db/database.ts +93 -0
- package/microservices/microservice-travel/src/db/migrations.ts +77 -0
- package/microservices/microservice-travel/src/db/travel.ts +802 -0
- package/microservices/microservice-travel/src/index.ts +60 -0
- package/microservices/microservice-travel/src/mcp/index.ts +495 -0
- package/microservices/microservice-wiki/package.json +27 -0
- package/microservices/microservice-wiki/src/cli/index.ts +345 -0
- package/microservices/microservice-wiki/src/db/database.ts +93 -0
- package/microservices/microservice-wiki/src/db/migrations.ts +55 -0
- package/microservices/microservice-wiki/src/db/wiki.ts +395 -0
- package/microservices/microservice-wiki/src/index.ts +32 -0
- package/microservices/microservice-wiki/src/mcp/index.ts +344 -0
- package/package.json +1 -1
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compliance CRUD operations — requirements, licenses, audits
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getDatabase } from "./database.js";
|
|
6
|
+
|
|
7
|
+
// ========================
|
|
8
|
+
// Types
|
|
9
|
+
// ========================
|
|
10
|
+
|
|
11
|
+
export type Framework = "gdpr" | "soc2" | "hipaa" | "pci" | "tax" | "iso27001" | "custom";
|
|
12
|
+
export type RequirementStatus = "compliant" | "non_compliant" | "in_progress" | "not_applicable";
|
|
13
|
+
export type LicenseType = "software" | "business" | "professional" | "patent" | "trademark";
|
|
14
|
+
export type LicenseStatus = "active" | "expired" | "pending_renewal";
|
|
15
|
+
export type AuditStatus = "scheduled" | "in_progress" | "completed" | "failed";
|
|
16
|
+
|
|
17
|
+
export interface Requirement {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
framework: Framework | null;
|
|
21
|
+
status: RequirementStatus;
|
|
22
|
+
description: string | null;
|
|
23
|
+
evidence: string | null;
|
|
24
|
+
due_date: string | null;
|
|
25
|
+
reviewed_at: string | null;
|
|
26
|
+
reviewer: string | null;
|
|
27
|
+
metadata: Record<string, unknown>;
|
|
28
|
+
created_at: string;
|
|
29
|
+
updated_at: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface RequirementRow {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
framework: string | null;
|
|
36
|
+
status: string;
|
|
37
|
+
description: string | null;
|
|
38
|
+
evidence: string | null;
|
|
39
|
+
due_date: string | null;
|
|
40
|
+
reviewed_at: string | null;
|
|
41
|
+
reviewer: string | null;
|
|
42
|
+
metadata: string;
|
|
43
|
+
created_at: string;
|
|
44
|
+
updated_at: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface License {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
type: LicenseType | null;
|
|
51
|
+
issuer: string | null;
|
|
52
|
+
license_number: string | null;
|
|
53
|
+
status: LicenseStatus;
|
|
54
|
+
issued_at: string | null;
|
|
55
|
+
expires_at: string | null;
|
|
56
|
+
auto_renew: boolean;
|
|
57
|
+
cost: number | null;
|
|
58
|
+
metadata: Record<string, unknown>;
|
|
59
|
+
created_at: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface LicenseRow {
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
type: string | null;
|
|
66
|
+
issuer: string | null;
|
|
67
|
+
license_number: string | null;
|
|
68
|
+
status: string;
|
|
69
|
+
issued_at: string | null;
|
|
70
|
+
expires_at: string | null;
|
|
71
|
+
auto_renew: number;
|
|
72
|
+
cost: number | null;
|
|
73
|
+
metadata: string;
|
|
74
|
+
created_at: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface Audit {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
framework: string | null;
|
|
81
|
+
status: AuditStatus;
|
|
82
|
+
findings: unknown[];
|
|
83
|
+
auditor: string | null;
|
|
84
|
+
scheduled_at: string | null;
|
|
85
|
+
completed_at: string | null;
|
|
86
|
+
created_at: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface AuditRow {
|
|
90
|
+
id: string;
|
|
91
|
+
name: string;
|
|
92
|
+
framework: string | null;
|
|
93
|
+
status: string;
|
|
94
|
+
findings: string;
|
|
95
|
+
auditor: string | null;
|
|
96
|
+
scheduled_at: string | null;
|
|
97
|
+
completed_at: string | null;
|
|
98
|
+
created_at: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ========================
|
|
102
|
+
// Row converters
|
|
103
|
+
// ========================
|
|
104
|
+
|
|
105
|
+
function rowToRequirement(row: RequirementRow): Requirement {
|
|
106
|
+
return {
|
|
107
|
+
...row,
|
|
108
|
+
framework: row.framework as Framework | null,
|
|
109
|
+
status: row.status as RequirementStatus,
|
|
110
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function rowToLicense(row: LicenseRow): License {
|
|
115
|
+
return {
|
|
116
|
+
...row,
|
|
117
|
+
type: row.type as LicenseType | null,
|
|
118
|
+
status: row.status as LicenseStatus,
|
|
119
|
+
auto_renew: row.auto_renew === 1,
|
|
120
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function rowToAudit(row: AuditRow): Audit {
|
|
125
|
+
return {
|
|
126
|
+
...row,
|
|
127
|
+
status: row.status as AuditStatus,
|
|
128
|
+
findings: JSON.parse(row.findings || "[]"),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ========================
|
|
133
|
+
// Requirements CRUD
|
|
134
|
+
// ========================
|
|
135
|
+
|
|
136
|
+
export interface CreateRequirementInput {
|
|
137
|
+
name: string;
|
|
138
|
+
framework?: Framework;
|
|
139
|
+
status?: RequirementStatus;
|
|
140
|
+
description?: string;
|
|
141
|
+
evidence?: string;
|
|
142
|
+
due_date?: string;
|
|
143
|
+
reviewer?: string;
|
|
144
|
+
metadata?: Record<string, unknown>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createRequirement(input: CreateRequirementInput): Requirement {
|
|
148
|
+
const db = getDatabase();
|
|
149
|
+
const id = crypto.randomUUID();
|
|
150
|
+
const metadata = JSON.stringify(input.metadata || {});
|
|
151
|
+
|
|
152
|
+
db.prepare(
|
|
153
|
+
`INSERT INTO requirements (id, name, framework, status, description, evidence, due_date, reviewer, metadata)
|
|
154
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
155
|
+
).run(
|
|
156
|
+
id,
|
|
157
|
+
input.name,
|
|
158
|
+
input.framework || null,
|
|
159
|
+
input.status || "in_progress",
|
|
160
|
+
input.description || null,
|
|
161
|
+
input.evidence || null,
|
|
162
|
+
input.due_date || null,
|
|
163
|
+
input.reviewer || null,
|
|
164
|
+
metadata
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return getRequirement(id)!;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function getRequirement(id: string): Requirement | null {
|
|
171
|
+
const db = getDatabase();
|
|
172
|
+
const row = db.prepare("SELECT * FROM requirements WHERE id = ?").get(id) as RequirementRow | null;
|
|
173
|
+
return row ? rowToRequirement(row) : null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ListRequirementsOptions {
|
|
177
|
+
framework?: string;
|
|
178
|
+
status?: string;
|
|
179
|
+
search?: string;
|
|
180
|
+
limit?: number;
|
|
181
|
+
offset?: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function listRequirements(options: ListRequirementsOptions = {}): Requirement[] {
|
|
185
|
+
const db = getDatabase();
|
|
186
|
+
const conditions: string[] = [];
|
|
187
|
+
const params: unknown[] = [];
|
|
188
|
+
|
|
189
|
+
if (options.framework) {
|
|
190
|
+
conditions.push("framework = ?");
|
|
191
|
+
params.push(options.framework);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (options.status) {
|
|
195
|
+
conditions.push("status = ?");
|
|
196
|
+
params.push(options.status);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (options.search) {
|
|
200
|
+
conditions.push("(name LIKE ? OR description LIKE ?)");
|
|
201
|
+
const q = `%${options.search}%`;
|
|
202
|
+
params.push(q, q);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let sql = "SELECT * FROM requirements";
|
|
206
|
+
if (conditions.length > 0) {
|
|
207
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
208
|
+
}
|
|
209
|
+
sql += " ORDER BY created_at DESC";
|
|
210
|
+
|
|
211
|
+
if (options.limit) {
|
|
212
|
+
sql += " LIMIT ?";
|
|
213
|
+
params.push(options.limit);
|
|
214
|
+
}
|
|
215
|
+
if (options.offset) {
|
|
216
|
+
sql += " OFFSET ?";
|
|
217
|
+
params.push(options.offset);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const rows = db.prepare(sql).all(...params) as RequirementRow[];
|
|
221
|
+
return rows.map(rowToRequirement);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface UpdateRequirementInput {
|
|
225
|
+
name?: string;
|
|
226
|
+
framework?: Framework;
|
|
227
|
+
status?: RequirementStatus;
|
|
228
|
+
description?: string;
|
|
229
|
+
evidence?: string;
|
|
230
|
+
due_date?: string;
|
|
231
|
+
reviewed_at?: string;
|
|
232
|
+
reviewer?: string;
|
|
233
|
+
metadata?: Record<string, unknown>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function updateRequirement(id: string, input: UpdateRequirementInput): Requirement | null {
|
|
237
|
+
const db = getDatabase();
|
|
238
|
+
const existing = getRequirement(id);
|
|
239
|
+
if (!existing) return null;
|
|
240
|
+
|
|
241
|
+
const sets: string[] = [];
|
|
242
|
+
const params: unknown[] = [];
|
|
243
|
+
|
|
244
|
+
if (input.name !== undefined) { sets.push("name = ?"); params.push(input.name); }
|
|
245
|
+
if (input.framework !== undefined) { sets.push("framework = ?"); params.push(input.framework); }
|
|
246
|
+
if (input.status !== undefined) { sets.push("status = ?"); params.push(input.status); }
|
|
247
|
+
if (input.description !== undefined) { sets.push("description = ?"); params.push(input.description); }
|
|
248
|
+
if (input.evidence !== undefined) { sets.push("evidence = ?"); params.push(input.evidence); }
|
|
249
|
+
if (input.due_date !== undefined) { sets.push("due_date = ?"); params.push(input.due_date); }
|
|
250
|
+
if (input.reviewed_at !== undefined) { sets.push("reviewed_at = ?"); params.push(input.reviewed_at); }
|
|
251
|
+
if (input.reviewer !== undefined) { sets.push("reviewer = ?"); params.push(input.reviewer); }
|
|
252
|
+
if (input.metadata !== undefined) { sets.push("metadata = ?"); params.push(JSON.stringify(input.metadata)); }
|
|
253
|
+
|
|
254
|
+
if (sets.length === 0) return existing;
|
|
255
|
+
|
|
256
|
+
sets.push("updated_at = datetime('now')");
|
|
257
|
+
params.push(id);
|
|
258
|
+
|
|
259
|
+
db.prepare(`UPDATE requirements SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
260
|
+
|
|
261
|
+
return getRequirement(id);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function deleteRequirement(id: string): boolean {
|
|
265
|
+
const db = getDatabase();
|
|
266
|
+
const result = db.prepare("DELETE FROM requirements WHERE id = ?").run(id);
|
|
267
|
+
return result.changes > 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function searchRequirements(query: string): Requirement[] {
|
|
271
|
+
return listRequirements({ search: query });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ========================
|
|
275
|
+
// Licenses CRUD
|
|
276
|
+
// ========================
|
|
277
|
+
|
|
278
|
+
export interface CreateLicenseInput {
|
|
279
|
+
name: string;
|
|
280
|
+
type?: LicenseType;
|
|
281
|
+
issuer?: string;
|
|
282
|
+
license_number?: string;
|
|
283
|
+
status?: LicenseStatus;
|
|
284
|
+
issued_at?: string;
|
|
285
|
+
expires_at?: string;
|
|
286
|
+
auto_renew?: boolean;
|
|
287
|
+
cost?: number;
|
|
288
|
+
metadata?: Record<string, unknown>;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function createLicense(input: CreateLicenseInput): License {
|
|
292
|
+
const db = getDatabase();
|
|
293
|
+
const id = crypto.randomUUID();
|
|
294
|
+
const metadata = JSON.stringify(input.metadata || {});
|
|
295
|
+
|
|
296
|
+
db.prepare(
|
|
297
|
+
`INSERT INTO licenses (id, name, type, issuer, license_number, status, issued_at, expires_at, auto_renew, cost, metadata)
|
|
298
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
299
|
+
).run(
|
|
300
|
+
id,
|
|
301
|
+
input.name,
|
|
302
|
+
input.type || null,
|
|
303
|
+
input.issuer || null,
|
|
304
|
+
input.license_number || null,
|
|
305
|
+
input.status || "active",
|
|
306
|
+
input.issued_at || null,
|
|
307
|
+
input.expires_at || null,
|
|
308
|
+
input.auto_renew ? 1 : 0,
|
|
309
|
+
input.cost ?? null,
|
|
310
|
+
metadata
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return getLicense(id)!;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function getLicense(id: string): License | null {
|
|
317
|
+
const db = getDatabase();
|
|
318
|
+
const row = db.prepare("SELECT * FROM licenses WHERE id = ?").get(id) as LicenseRow | null;
|
|
319
|
+
return row ? rowToLicense(row) : null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export interface ListLicensesOptions {
|
|
323
|
+
type?: string;
|
|
324
|
+
status?: string;
|
|
325
|
+
search?: string;
|
|
326
|
+
limit?: number;
|
|
327
|
+
offset?: number;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function listLicenses(options: ListLicensesOptions = {}): License[] {
|
|
331
|
+
const db = getDatabase();
|
|
332
|
+
const conditions: string[] = [];
|
|
333
|
+
const params: unknown[] = [];
|
|
334
|
+
|
|
335
|
+
if (options.type) {
|
|
336
|
+
conditions.push("type = ?");
|
|
337
|
+
params.push(options.type);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (options.status) {
|
|
341
|
+
conditions.push("status = ?");
|
|
342
|
+
params.push(options.status);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (options.search) {
|
|
346
|
+
conditions.push("(name LIKE ? OR issuer LIKE ? OR license_number LIKE ?)");
|
|
347
|
+
const q = `%${options.search}%`;
|
|
348
|
+
params.push(q, q, q);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let sql = "SELECT * FROM licenses";
|
|
352
|
+
if (conditions.length > 0) {
|
|
353
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
354
|
+
}
|
|
355
|
+
sql += " ORDER BY created_at DESC";
|
|
356
|
+
|
|
357
|
+
if (options.limit) {
|
|
358
|
+
sql += " LIMIT ?";
|
|
359
|
+
params.push(options.limit);
|
|
360
|
+
}
|
|
361
|
+
if (options.offset) {
|
|
362
|
+
sql += " OFFSET ?";
|
|
363
|
+
params.push(options.offset);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const rows = db.prepare(sql).all(...params) as LicenseRow[];
|
|
367
|
+
return rows.map(rowToLicense);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export interface UpdateLicenseInput {
|
|
371
|
+
name?: string;
|
|
372
|
+
type?: LicenseType;
|
|
373
|
+
issuer?: string;
|
|
374
|
+
license_number?: string;
|
|
375
|
+
status?: LicenseStatus;
|
|
376
|
+
issued_at?: string;
|
|
377
|
+
expires_at?: string;
|
|
378
|
+
auto_renew?: boolean;
|
|
379
|
+
cost?: number;
|
|
380
|
+
metadata?: Record<string, unknown>;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function updateLicense(id: string, input: UpdateLicenseInput): License | null {
|
|
384
|
+
const db = getDatabase();
|
|
385
|
+
const existing = getLicense(id);
|
|
386
|
+
if (!existing) return null;
|
|
387
|
+
|
|
388
|
+
const sets: string[] = [];
|
|
389
|
+
const params: unknown[] = [];
|
|
390
|
+
|
|
391
|
+
if (input.name !== undefined) { sets.push("name = ?"); params.push(input.name); }
|
|
392
|
+
if (input.type !== undefined) { sets.push("type = ?"); params.push(input.type); }
|
|
393
|
+
if (input.issuer !== undefined) { sets.push("issuer = ?"); params.push(input.issuer); }
|
|
394
|
+
if (input.license_number !== undefined) { sets.push("license_number = ?"); params.push(input.license_number); }
|
|
395
|
+
if (input.status !== undefined) { sets.push("status = ?"); params.push(input.status); }
|
|
396
|
+
if (input.issued_at !== undefined) { sets.push("issued_at = ?"); params.push(input.issued_at); }
|
|
397
|
+
if (input.expires_at !== undefined) { sets.push("expires_at = ?"); params.push(input.expires_at); }
|
|
398
|
+
if (input.auto_renew !== undefined) { sets.push("auto_renew = ?"); params.push(input.auto_renew ? 1 : 0); }
|
|
399
|
+
if (input.cost !== undefined) { sets.push("cost = ?"); params.push(input.cost); }
|
|
400
|
+
if (input.metadata !== undefined) { sets.push("metadata = ?"); params.push(JSON.stringify(input.metadata)); }
|
|
401
|
+
|
|
402
|
+
if (sets.length === 0) return existing;
|
|
403
|
+
|
|
404
|
+
params.push(id);
|
|
405
|
+
|
|
406
|
+
db.prepare(`UPDATE licenses SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
407
|
+
|
|
408
|
+
return getLicense(id);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function deleteLicense(id: string): boolean {
|
|
412
|
+
const db = getDatabase();
|
|
413
|
+
const result = db.prepare("DELETE FROM licenses WHERE id = ?").run(id);
|
|
414
|
+
return result.changes > 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function renewLicense(id: string, newExpiresAt: string): License | null {
|
|
418
|
+
return updateLicense(id, { status: "active", expires_at: newExpiresAt });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function listExpiringLicenses(days: number): License[] {
|
|
422
|
+
const db = getDatabase();
|
|
423
|
+
const rows = db.prepare(
|
|
424
|
+
`SELECT * FROM licenses
|
|
425
|
+
WHERE status = 'active'
|
|
426
|
+
AND expires_at IS NOT NULL
|
|
427
|
+
AND expires_at <= datetime('now', '+' || ? || ' days')
|
|
428
|
+
ORDER BY expires_at ASC`
|
|
429
|
+
).all(days) as LicenseRow[];
|
|
430
|
+
return rows.map(rowToLicense);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function getLicenseStats(): {
|
|
434
|
+
total: number;
|
|
435
|
+
active: number;
|
|
436
|
+
expired: number;
|
|
437
|
+
pending_renewal: number;
|
|
438
|
+
total_cost: number;
|
|
439
|
+
by_type: Record<string, number>;
|
|
440
|
+
} {
|
|
441
|
+
const db = getDatabase();
|
|
442
|
+
|
|
443
|
+
const total = (db.prepare("SELECT COUNT(*) as count FROM licenses").get() as { count: number }).count;
|
|
444
|
+
const active = (db.prepare("SELECT COUNT(*) as count FROM licenses WHERE status = 'active'").get() as { count: number }).count;
|
|
445
|
+
const expired = (db.prepare("SELECT COUNT(*) as count FROM licenses WHERE status = 'expired'").get() as { count: number }).count;
|
|
446
|
+
const pending_renewal = (db.prepare("SELECT COUNT(*) as count FROM licenses WHERE status = 'pending_renewal'").get() as { count: number }).count;
|
|
447
|
+
const costRow = db.prepare("SELECT COALESCE(SUM(cost), 0) as total FROM licenses").get() as { total: number };
|
|
448
|
+
|
|
449
|
+
const typeRows = db.prepare(
|
|
450
|
+
"SELECT type, COUNT(*) as count FROM licenses WHERE type IS NOT NULL GROUP BY type"
|
|
451
|
+
).all() as { type: string; count: number }[];
|
|
452
|
+
|
|
453
|
+
const by_type: Record<string, number> = {};
|
|
454
|
+
for (const row of typeRows) {
|
|
455
|
+
by_type[row.type] = row.count;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return { total, active, expired, pending_renewal, total_cost: costRow.total, by_type };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// ========================
|
|
462
|
+
// Audits CRUD
|
|
463
|
+
// ========================
|
|
464
|
+
|
|
465
|
+
export interface CreateAuditInput {
|
|
466
|
+
name: string;
|
|
467
|
+
framework?: string;
|
|
468
|
+
auditor?: string;
|
|
469
|
+
scheduled_at?: string;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function scheduleAudit(input: CreateAuditInput): Audit {
|
|
473
|
+
const db = getDatabase();
|
|
474
|
+
const id = crypto.randomUUID();
|
|
475
|
+
|
|
476
|
+
db.prepare(
|
|
477
|
+
`INSERT INTO audits (id, name, framework, status, auditor, scheduled_at)
|
|
478
|
+
VALUES (?, ?, ?, 'scheduled', ?, ?)`
|
|
479
|
+
).run(
|
|
480
|
+
id,
|
|
481
|
+
input.name,
|
|
482
|
+
input.framework || null,
|
|
483
|
+
input.auditor || null,
|
|
484
|
+
input.scheduled_at || null
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
return getAudit(id)!;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function getAudit(id: string): Audit | null {
|
|
491
|
+
const db = getDatabase();
|
|
492
|
+
const row = db.prepare("SELECT * FROM audits WHERE id = ?").get(id) as AuditRow | null;
|
|
493
|
+
return row ? rowToAudit(row) : null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export interface ListAuditsOptions {
|
|
497
|
+
framework?: string;
|
|
498
|
+
status?: string;
|
|
499
|
+
limit?: number;
|
|
500
|
+
offset?: number;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function listAudits(options: ListAuditsOptions = {}): Audit[] {
|
|
504
|
+
const db = getDatabase();
|
|
505
|
+
const conditions: string[] = [];
|
|
506
|
+
const params: unknown[] = [];
|
|
507
|
+
|
|
508
|
+
if (options.framework) {
|
|
509
|
+
conditions.push("framework = ?");
|
|
510
|
+
params.push(options.framework);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (options.status) {
|
|
514
|
+
conditions.push("status = ?");
|
|
515
|
+
params.push(options.status);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let sql = "SELECT * FROM audits";
|
|
519
|
+
if (conditions.length > 0) {
|
|
520
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
521
|
+
}
|
|
522
|
+
sql += " ORDER BY created_at DESC";
|
|
523
|
+
|
|
524
|
+
if (options.limit) {
|
|
525
|
+
sql += " LIMIT ?";
|
|
526
|
+
params.push(options.limit);
|
|
527
|
+
}
|
|
528
|
+
if (options.offset) {
|
|
529
|
+
sql += " OFFSET ?";
|
|
530
|
+
params.push(options.offset);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const rows = db.prepare(sql).all(...params) as AuditRow[];
|
|
534
|
+
return rows.map(rowToAudit);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function completeAudit(id: string, findings: unknown[]): Audit | null {
|
|
538
|
+
const db = getDatabase();
|
|
539
|
+
const existing = getAudit(id);
|
|
540
|
+
if (!existing) return null;
|
|
541
|
+
|
|
542
|
+
const hasCritical = findings.some(
|
|
543
|
+
(f: unknown) => typeof f === "object" && f !== null && (f as Record<string, unknown>).severity === "critical"
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
db.prepare(
|
|
547
|
+
`UPDATE audits SET status = ?, findings = ?, completed_at = datetime('now') WHERE id = ?`
|
|
548
|
+
).run(hasCritical ? "failed" : "completed", JSON.stringify(findings), id);
|
|
549
|
+
|
|
550
|
+
return getAudit(id);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export function getAuditReport(id: string): {
|
|
554
|
+
audit: Audit;
|
|
555
|
+
summary: { total_findings: number; by_severity: Record<string, number> };
|
|
556
|
+
} | null {
|
|
557
|
+
const audit = getAudit(id);
|
|
558
|
+
if (!audit) return null;
|
|
559
|
+
|
|
560
|
+
const by_severity: Record<string, number> = {};
|
|
561
|
+
for (const finding of audit.findings) {
|
|
562
|
+
if (typeof finding === "object" && finding !== null) {
|
|
563
|
+
const severity = (finding as Record<string, unknown>).severity as string || "unknown";
|
|
564
|
+
by_severity[severity] = (by_severity[severity] || 0) + 1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
audit,
|
|
570
|
+
summary: {
|
|
571
|
+
total_findings: audit.findings.length,
|
|
572
|
+
by_severity,
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function deleteAudit(id: string): boolean {
|
|
578
|
+
const db = getDatabase();
|
|
579
|
+
const result = db.prepare("DELETE FROM audits WHERE id = ?").run(id);
|
|
580
|
+
return result.changes > 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ========================
|
|
584
|
+
// Analytics / Scoring
|
|
585
|
+
// ========================
|
|
586
|
+
|
|
587
|
+
export function getComplianceScore(): {
|
|
588
|
+
total: number;
|
|
589
|
+
compliant: number;
|
|
590
|
+
non_compliant: number;
|
|
591
|
+
in_progress: number;
|
|
592
|
+
not_applicable: number;
|
|
593
|
+
score: number;
|
|
594
|
+
} {
|
|
595
|
+
const db = getDatabase();
|
|
596
|
+
const total = (db.prepare("SELECT COUNT(*) as count FROM requirements").get() as { count: number }).count;
|
|
597
|
+
const compliant = (db.prepare("SELECT COUNT(*) as count FROM requirements WHERE status = 'compliant'").get() as { count: number }).count;
|
|
598
|
+
const non_compliant = (db.prepare("SELECT COUNT(*) as count FROM requirements WHERE status = 'non_compliant'").get() as { count: number }).count;
|
|
599
|
+
const in_progress = (db.prepare("SELECT COUNT(*) as count FROM requirements WHERE status = 'in_progress'").get() as { count: number }).count;
|
|
600
|
+
const not_applicable = (db.prepare("SELECT COUNT(*) as count FROM requirements WHERE status = 'not_applicable'").get() as { count: number }).count;
|
|
601
|
+
|
|
602
|
+
// Score = % of applicable requirements that are compliant
|
|
603
|
+
const applicable = total - not_applicable;
|
|
604
|
+
const score = applicable > 0 ? Math.round((compliant / applicable) * 100) : 100;
|
|
605
|
+
|
|
606
|
+
return { total, compliant, non_compliant, in_progress, not_applicable, score };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function getFrameworkStatus(framework: string): {
|
|
610
|
+
framework: string;
|
|
611
|
+
total: number;
|
|
612
|
+
compliant: number;
|
|
613
|
+
non_compliant: number;
|
|
614
|
+
in_progress: number;
|
|
615
|
+
not_applicable: number;
|
|
616
|
+
score: number;
|
|
617
|
+
requirements: Requirement[];
|
|
618
|
+
} {
|
|
619
|
+
const db = getDatabase();
|
|
620
|
+
const rows = db.prepare("SELECT * FROM requirements WHERE framework = ? ORDER BY name").all(framework) as RequirementRow[];
|
|
621
|
+
const requirements = rows.map(rowToRequirement);
|
|
622
|
+
|
|
623
|
+
const total = requirements.length;
|
|
624
|
+
const compliant = requirements.filter((r) => r.status === "compliant").length;
|
|
625
|
+
const non_compliant = requirements.filter((r) => r.status === "non_compliant").length;
|
|
626
|
+
const in_progress = requirements.filter((r) => r.status === "in_progress").length;
|
|
627
|
+
const not_applicable = requirements.filter((r) => r.status === "not_applicable").length;
|
|
628
|
+
|
|
629
|
+
const applicable = total - not_applicable;
|
|
630
|
+
const score = applicable > 0 ? Math.round((compliant / applicable) * 100) : 100;
|
|
631
|
+
|
|
632
|
+
return { framework, total, compliant, non_compliant, in_progress, not_applicable, score, requirements };
|
|
633
|
+
}
|