@wopr-network/platform-core 1.12.2 → 1.13.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/dist/api/routes/activity.d.ts +9 -0
- package/dist/api/routes/activity.js +68 -0
- package/dist/api/routes/admin-audit-helper.d.ts +7 -0
- package/dist/api/routes/admin-audit-helper.js +13 -0
- package/dist/api/routes/admin-audit.d.ts +13 -0
- package/dist/api/routes/admin-audit.js +61 -0
- package/dist/api/routes/admin-backups.d.ts +19 -0
- package/dist/api/routes/admin-backups.js +116 -0
- package/dist/api/routes/admin-compliance.d.ts +9 -0
- package/dist/api/routes/admin-compliance.js +27 -0
- package/dist/api/routes/admin-credits.d.ts +9 -0
- package/dist/api/routes/admin-credits.js +255 -0
- package/dist/api/routes/admin-gpu.d.ts +46 -0
- package/dist/api/routes/admin-gpu.js +140 -0
- package/dist/api/routes/admin-inference.d.ts +16 -0
- package/dist/api/routes/admin-inference.js +98 -0
- package/dist/api/routes/admin-marketplace.d.ts +36 -0
- package/dist/api/routes/admin-marketplace.js +181 -0
- package/dist/api/routes/admin-migration.d.ts +10 -0
- package/dist/api/routes/admin-migration.js +46 -0
- package/dist/api/routes/admin-notes.d.ts +34 -0
- package/dist/api/routes/admin-notes.js +131 -0
- package/dist/api/routes/admin-onboarding.d.ts +7 -0
- package/dist/api/routes/admin-onboarding.js +49 -0
- package/dist/api/routes/admin-rates.d.ts +9 -0
- package/dist/api/routes/admin-rates.js +427 -0
- package/dist/api/routes/admin-recovery.d.ts +91 -0
- package/dist/api/routes/admin-recovery.js +246 -0
- package/dist/api/routes/admin-roles.d.ts +27 -0
- package/dist/api/routes/admin-roles.js +157 -0
- package/dist/api/routes/audit.d.ts +19 -0
- package/dist/api/routes/audit.js +95 -0
- package/dist/api/routes/auth.d.ts +19 -0
- package/dist/api/routes/auth.js +25 -0
- package/dist/api/routes/channel-validate.d.ts +11 -0
- package/dist/api/routes/channel-validate.js +148 -0
- package/dist/api/routes/fleet-events.d.ts +4 -0
- package/dist/api/routes/fleet-events.js +53 -0
- package/dist/api/routes/friends-proxy.d.ts +28 -0
- package/dist/api/routes/friends-proxy.js +63 -0
- package/dist/api/routes/friends-types.d.ts +34 -0
- package/dist/api/routes/friends-types.js +28 -0
- package/dist/api/routes/health.d.ts +14 -0
- package/dist/api/routes/health.js +32 -0
- package/dist/api/routes/health.test.d.ts +1 -0
- package/dist/api/routes/health.test.js +70 -0
- package/dist/api/routes/incident-response.d.ts +9 -0
- package/dist/api/routes/incident-response.js +148 -0
- package/dist/api/routes/internal-gpu.d.ts +12 -0
- package/dist/api/routes/internal-gpu.js +70 -0
- package/dist/api/routes/internal-nodes.d.ts +41 -0
- package/dist/api/routes/internal-nodes.js +105 -0
- package/dist/api/routes/login-history.d.ts +11 -0
- package/dist/api/routes/login-history.js +22 -0
- package/dist/api/routes/public-pricing.d.ts +9 -0
- package/dist/api/routes/public-pricing.js +32 -0
- package/dist/api/routes/quota.d.ts +8 -0
- package/dist/api/routes/quota.js +113 -0
- package/dist/api/routes/secret-audit.d.ts +12 -0
- package/dist/api/routes/secret-audit.js +41 -0
- package/dist/api/routes/secrets.d.ts +31 -0
- package/dist/api/routes/secrets.js +135 -0
- package/dist/api/routes/tenant-keys.d.ts +16 -0
- package/dist/api/routes/tenant-keys.js +142 -0
- package/dist/api/routes/verify-email.d.ts +19 -0
- package/dist/api/routes/verify-email.js +70 -0
- package/dist/api/routes/ws-auth.d.ts +21 -0
- package/dist/api/routes/ws-auth.js +24 -0
- package/dist/monetization/adapters/bootstrap.d.ts +2 -2
- package/dist/monetization/adapters/bootstrap.js +3 -2
- package/dist/monetization/adapters/bootstrap.test.js +11 -7
- package/dist/monetization/adapters/embeddings-factory.d.ts +10 -5
- package/dist/monetization/adapters/embeddings-factory.js +17 -4
- package/dist/monetization/adapters/embeddings-factory.test.js +85 -31
- package/dist/monetization/adapters/ollama-embeddings.d.ts +40 -0
- package/dist/monetization/adapters/ollama-embeddings.js +76 -0
- package/dist/monetization/adapters/ollama-embeddings.test.d.ts +1 -0
- package/dist/monetization/adapters/ollama-embeddings.test.js +178 -0
- package/dist/monetization/adapters/rate-table.js +9 -3
- package/dist/monetization/adapters/rate-table.test.js +22 -1
- package/package.json +35 -1
- package/src/api/routes/activity.ts +77 -0
- package/src/api/routes/admin-audit-helper.ts +18 -0
- package/src/api/routes/admin-audit.ts +67 -0
- package/src/api/routes/admin-backups.ts +134 -0
- package/src/api/routes/admin-compliance.ts +35 -0
- package/src/api/routes/admin-credits.ts +280 -0
- package/src/api/routes/admin-gpu.ts +202 -0
- package/src/api/routes/admin-inference.ts +109 -0
- package/src/api/routes/admin-marketplace.ts +233 -0
- package/src/api/routes/admin-migration.ts +61 -0
- package/src/api/routes/admin-notes.ts +145 -0
- package/src/api/routes/admin-onboarding.ts +62 -0
- package/src/api/routes/admin-rates.ts +462 -0
- package/src/api/routes/admin-recovery.ts +376 -0
- package/src/api/routes/admin-roles.ts +205 -0
- package/src/api/routes/audit.ts +106 -0
- package/src/api/routes/auth.ts +30 -0
- package/src/api/routes/channel-validate.ts +182 -0
- package/src/api/routes/fleet-events.ts +66 -0
- package/src/api/routes/friends-proxy.ts +94 -0
- package/src/api/routes/friends-types.ts +37 -0
- package/src/api/routes/health.test.ts +80 -0
- package/src/api/routes/health.ts +48 -0
- package/src/api/routes/incident-response.ts +159 -0
- package/src/api/routes/internal-gpu.ts +92 -0
- package/src/api/routes/internal-nodes.ts +157 -0
- package/src/api/routes/login-history.ts +28 -0
- package/src/api/routes/public-pricing.ts +36 -0
- package/src/api/routes/quota.ts +136 -0
- package/src/api/routes/secret-audit.ts +55 -0
- package/src/api/routes/secrets.ts +178 -0
- package/src/api/routes/tenant-keys.ts +178 -0
- package/src/api/routes/verify-email.ts +102 -0
- package/src/api/routes/ws-auth.ts +44 -0
- package/src/monetization/adapters/bootstrap.test.ts +11 -7
- package/src/monetization/adapters/bootstrap.ts +3 -2
- package/src/monetization/adapters/embeddings-factory.test.ts +102 -33
- package/src/monetization/adapters/embeddings-factory.ts +24 -7
- package/src/monetization/adapters/ollama-embeddings.test.ts +235 -0
- package/src/monetization/adapters/ollama-embeddings.ts +120 -0
- package/src/monetization/adapters/rate-table.test.ts +32 -1
- package/src/monetization/adapters/rate-table.ts +9 -3
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { safeAuditLog } from "./admin-audit-helper.js";
|
|
3
|
+
function parseIntParam(value) {
|
|
4
|
+
if (value == null)
|
|
5
|
+
return undefined;
|
|
6
|
+
const n = Number(value);
|
|
7
|
+
return Number.isFinite(n) ? n : undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create admin notes API routes.
|
|
11
|
+
* Pass a store factory and optional audit logger for DI.
|
|
12
|
+
*/
|
|
13
|
+
export function createAdminNotesApiRoutes(storeFactory, auditLogger) {
|
|
14
|
+
const routes = new Hono();
|
|
15
|
+
// GET /:tenantId -- list notes
|
|
16
|
+
routes.get("/:tenantId", async (c) => {
|
|
17
|
+
const store = storeFactory();
|
|
18
|
+
const tenantId = c.req.param("tenantId");
|
|
19
|
+
const filters = {
|
|
20
|
+
tenantId,
|
|
21
|
+
limit: parseIntParam(c.req.query("limit")),
|
|
22
|
+
offset: parseIntParam(c.req.query("offset")),
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
const result = await store.list(filters);
|
|
26
|
+
return c.json(result);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
// POST /:tenantId -- create note
|
|
33
|
+
routes.post("/:tenantId", async (c) => {
|
|
34
|
+
const store = storeFactory();
|
|
35
|
+
const tenantId = c.req.param("tenantId");
|
|
36
|
+
let body;
|
|
37
|
+
try {
|
|
38
|
+
body = (await c.req.json());
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
42
|
+
}
|
|
43
|
+
const content = body.content;
|
|
44
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
45
|
+
return c.json({ error: "content is required and must be non-empty" }, 400);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const user = c.get("user");
|
|
49
|
+
const note = await store.create({
|
|
50
|
+
tenantId,
|
|
51
|
+
authorId: user?.id ?? "unknown",
|
|
52
|
+
content,
|
|
53
|
+
isPinned: body.isPinned === true,
|
|
54
|
+
});
|
|
55
|
+
safeAuditLog(auditLogger, {
|
|
56
|
+
adminUser: user?.id ?? "unknown",
|
|
57
|
+
action: "note.create",
|
|
58
|
+
category: "support",
|
|
59
|
+
targetTenant: tenantId,
|
|
60
|
+
details: { noteId: note.id },
|
|
61
|
+
outcome: "success",
|
|
62
|
+
});
|
|
63
|
+
return c.json(note, 201);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return c.json({ error: err instanceof Error ? err.message : "Internal server error" }, 500);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// PATCH /:tenantId/:noteId -- update note
|
|
70
|
+
routes.patch("/:tenantId/:noteId", async (c) => {
|
|
71
|
+
const store = storeFactory();
|
|
72
|
+
const tenantId = c.req.param("tenantId");
|
|
73
|
+
const noteId = c.req.param("noteId");
|
|
74
|
+
let body;
|
|
75
|
+
try {
|
|
76
|
+
body = (await c.req.json());
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
80
|
+
}
|
|
81
|
+
const updates = {};
|
|
82
|
+
if (typeof body.content === "string")
|
|
83
|
+
updates.content = body.content;
|
|
84
|
+
if (typeof body.isPinned === "boolean")
|
|
85
|
+
updates.isPinned = body.isPinned;
|
|
86
|
+
try {
|
|
87
|
+
const note = await store.update(noteId, tenantId, updates);
|
|
88
|
+
if (note === null) {
|
|
89
|
+
return c.json({ error: "Forbidden" }, 403);
|
|
90
|
+
}
|
|
91
|
+
const user = c.get("user");
|
|
92
|
+
safeAuditLog(auditLogger, {
|
|
93
|
+
adminUser: user?.id ?? "unknown",
|
|
94
|
+
action: "note.update",
|
|
95
|
+
category: "support",
|
|
96
|
+
targetTenant: tenantId,
|
|
97
|
+
details: { noteId, hasContentChange: !!updates.content, hasPinChange: updates.isPinned !== undefined },
|
|
98
|
+
outcome: "success",
|
|
99
|
+
});
|
|
100
|
+
return c.json(note);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return c.json({ error: err instanceof Error ? err.message : "Internal server error" }, 500);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
// DELETE /:tenantId/:noteId -- delete note
|
|
107
|
+
routes.delete("/:tenantId/:noteId", async (c) => {
|
|
108
|
+
const store = storeFactory();
|
|
109
|
+
const tenantId = c.req.param("tenantId");
|
|
110
|
+
const noteId = c.req.param("noteId");
|
|
111
|
+
try {
|
|
112
|
+
const deleted = await store.delete(noteId, tenantId);
|
|
113
|
+
if (!deleted)
|
|
114
|
+
return c.json({ error: "Forbidden" }, 403);
|
|
115
|
+
const user = c.get("user");
|
|
116
|
+
safeAuditLog(auditLogger, {
|
|
117
|
+
adminUser: user?.id ?? "unknown",
|
|
118
|
+
action: "note.delete",
|
|
119
|
+
category: "support",
|
|
120
|
+
targetTenant: tenantId,
|
|
121
|
+
details: { noteId },
|
|
122
|
+
outcome: "success",
|
|
123
|
+
});
|
|
124
|
+
return c.json({ ok: true });
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return routes;
|
|
131
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { AuthEnv } from "../../auth/index.js";
|
|
3
|
+
import type { IOnboardingScriptRepository } from "../../onboarding/drizzle-onboarding-script-repository.js";
|
|
4
|
+
import type { AdminAuditLogger } from "./admin-audit-helper.js";
|
|
5
|
+
type RepoFactory = () => IOnboardingScriptRepository;
|
|
6
|
+
export declare function createAdminOnboardingRoutes(getRepo: RepoFactory, auditLogger?: () => AdminAuditLogger): Hono<AuthEnv>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { safeAuditLog } from "./admin-audit-helper.js";
|
|
3
|
+
export function createAdminOnboardingRoutes(getRepo, auditLogger) {
|
|
4
|
+
const routes = new Hono();
|
|
5
|
+
routes.get("/current", async (c) => {
|
|
6
|
+
const repo = getRepo();
|
|
7
|
+
const script = await repo.findCurrent();
|
|
8
|
+
if (!script) {
|
|
9
|
+
return c.json({ error: "No onboarding script found" }, 404);
|
|
10
|
+
}
|
|
11
|
+
return c.json(script);
|
|
12
|
+
});
|
|
13
|
+
routes.get("/history", async (c) => {
|
|
14
|
+
const repo = getRepo();
|
|
15
|
+
const limitParam = c.req.query("limit");
|
|
16
|
+
const limit = limitParam ? Math.min(50, Math.max(1, Number(limitParam) || 10)) : 10;
|
|
17
|
+
const history = await repo.findHistory(limit);
|
|
18
|
+
return c.json(history);
|
|
19
|
+
});
|
|
20
|
+
routes.post("/", async (c) => {
|
|
21
|
+
const repo = getRepo();
|
|
22
|
+
let body;
|
|
23
|
+
try {
|
|
24
|
+
body = (await c.req.json());
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
28
|
+
}
|
|
29
|
+
const content = body.content;
|
|
30
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
31
|
+
return c.json({ error: "content is required and must be non-empty" }, 400);
|
|
32
|
+
}
|
|
33
|
+
const user = c.get("user");
|
|
34
|
+
const adminUser = user?.id ?? "unknown";
|
|
35
|
+
const script = await repo.insert({
|
|
36
|
+
content,
|
|
37
|
+
updatedBy: adminUser !== "unknown" ? adminUser : null,
|
|
38
|
+
});
|
|
39
|
+
safeAuditLog(auditLogger, {
|
|
40
|
+
adminUser,
|
|
41
|
+
action: "onboarding.script_updated",
|
|
42
|
+
category: "config",
|
|
43
|
+
details: { version: script.version },
|
|
44
|
+
outcome: "success",
|
|
45
|
+
});
|
|
46
|
+
return c.json(script, 201);
|
|
47
|
+
});
|
|
48
|
+
return routes;
|
|
49
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { RateStore } from "../../admin/rates/rate-store.js";
|
|
3
|
+
import type { AuthEnv } from "../../auth/index.js";
|
|
4
|
+
import type { AdminAuditLogger } from "./admin-audit-helper.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create admin rate API routes.
|
|
7
|
+
* Pass a store factory and optional audit logger for DI.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createAdminRateApiRoutes(storeFactory: () => RateStore, auditLogger?: () => AdminAuditLogger): Hono<AuthEnv>;
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { safeAuditLog } from "./admin-audit-helper.js";
|
|
3
|
+
function parseBooleanParam(value) {
|
|
4
|
+
if (value === "true")
|
|
5
|
+
return true;
|
|
6
|
+
if (value === "false")
|
|
7
|
+
return false;
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create admin rate API routes.
|
|
12
|
+
* Pass a store factory and optional audit logger for DI.
|
|
13
|
+
*/
|
|
14
|
+
export function createAdminRateApiRoutes(storeFactory, auditLogger) {
|
|
15
|
+
const routes = new Hono();
|
|
16
|
+
// ── Combined List ──
|
|
17
|
+
routes.get("/", async (c) => {
|
|
18
|
+
const store = storeFactory();
|
|
19
|
+
const capability = c.req.query("capability");
|
|
20
|
+
const active = parseBooleanParam(c.req.query("active"));
|
|
21
|
+
try {
|
|
22
|
+
const [sellRates, providerCosts] = await Promise.all([
|
|
23
|
+
store.listSellRates({ capability, isActive: active }),
|
|
24
|
+
store.listProviderCosts({ capability, isActive: active }),
|
|
25
|
+
]);
|
|
26
|
+
return c.json({
|
|
27
|
+
sell_rates: sellRates.entries,
|
|
28
|
+
provider_costs: providerCosts.entries,
|
|
29
|
+
total_sell_rates: sellRates.total,
|
|
30
|
+
total_provider_costs: providerCosts.total,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// ── Sell Rates ──
|
|
38
|
+
routes.post("/sell", async (c) => {
|
|
39
|
+
const store = storeFactory();
|
|
40
|
+
let body;
|
|
41
|
+
try {
|
|
42
|
+
body = (await c.req.json());
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
46
|
+
}
|
|
47
|
+
const { capability, displayName, unit, priceUsd, model, isActive, sortOrder } = body;
|
|
48
|
+
if (typeof capability !== "string" || !capability.trim()) {
|
|
49
|
+
return c.json({ error: "capability is required and must be non-empty" }, 400);
|
|
50
|
+
}
|
|
51
|
+
if (typeof displayName !== "string" || !displayName.trim()) {
|
|
52
|
+
return c.json({ error: "displayName is required and must be non-empty" }, 400);
|
|
53
|
+
}
|
|
54
|
+
if (typeof unit !== "string" || !unit.trim()) {
|
|
55
|
+
return c.json({ error: "unit is required and must be non-empty" }, 400);
|
|
56
|
+
}
|
|
57
|
+
if (typeof priceUsd !== "number" || priceUsd <= 0) {
|
|
58
|
+
return c.json({ error: "priceUsd must be a positive number" }, 400);
|
|
59
|
+
}
|
|
60
|
+
if (model !== undefined && typeof model !== "string") {
|
|
61
|
+
return c.json({ error: "model must be a string if provided" }, 400);
|
|
62
|
+
}
|
|
63
|
+
if (isActive !== undefined && typeof isActive !== "boolean") {
|
|
64
|
+
return c.json({ error: "isActive must be a boolean if provided" }, 400);
|
|
65
|
+
}
|
|
66
|
+
if (sortOrder !== undefined && (typeof sortOrder !== "number" || !Number.isInteger(sortOrder))) {
|
|
67
|
+
return c.json({ error: "sortOrder must be an integer if provided" }, 400);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const adminUser = c.get("user")?.id ?? "unknown";
|
|
71
|
+
const input = {
|
|
72
|
+
capability,
|
|
73
|
+
displayName,
|
|
74
|
+
unit,
|
|
75
|
+
priceUsd,
|
|
76
|
+
model: model,
|
|
77
|
+
isActive: isActive,
|
|
78
|
+
sortOrder: sortOrder,
|
|
79
|
+
};
|
|
80
|
+
let result;
|
|
81
|
+
try {
|
|
82
|
+
result = await store.createSellRate(input);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
safeAuditLog(auditLogger, {
|
|
86
|
+
adminUser,
|
|
87
|
+
action: "rates.createSellRate",
|
|
88
|
+
category: "config",
|
|
89
|
+
details: { ...input, error: String(err) },
|
|
90
|
+
outcome: "failure",
|
|
91
|
+
});
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
safeAuditLog(auditLogger, {
|
|
95
|
+
adminUser,
|
|
96
|
+
action: "rates.createSellRate",
|
|
97
|
+
category: "config",
|
|
98
|
+
details: { ...input },
|
|
99
|
+
outcome: "success",
|
|
100
|
+
});
|
|
101
|
+
return c.json(result, 201);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return c.json({ error: err instanceof Error ? err.message : "Internal server error" }, 500);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
routes.put("/sell/:id", async (c) => {
|
|
108
|
+
const store = storeFactory();
|
|
109
|
+
const id = c.req.param("id");
|
|
110
|
+
let body;
|
|
111
|
+
try {
|
|
112
|
+
body = (await c.req.json());
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
116
|
+
}
|
|
117
|
+
const { capability, displayName, unit, priceUsd, model, isActive, sortOrder } = body;
|
|
118
|
+
if (capability !== undefined && (typeof capability !== "string" || !capability.trim())) {
|
|
119
|
+
return c.json({ error: "capability must be non-empty if provided" }, 400);
|
|
120
|
+
}
|
|
121
|
+
if (displayName !== undefined && (typeof displayName !== "string" || !displayName.trim())) {
|
|
122
|
+
return c.json({ error: "displayName must be non-empty if provided" }, 400);
|
|
123
|
+
}
|
|
124
|
+
if (unit !== undefined && (typeof unit !== "string" || !unit.trim())) {
|
|
125
|
+
return c.json({ error: "unit must be non-empty if provided" }, 400);
|
|
126
|
+
}
|
|
127
|
+
if (priceUsd !== undefined && (typeof priceUsd !== "number" || priceUsd <= 0)) {
|
|
128
|
+
return c.json({ error: "priceUsd must be a positive number if provided" }, 400);
|
|
129
|
+
}
|
|
130
|
+
if (model !== undefined && model !== null && typeof model !== "string") {
|
|
131
|
+
return c.json({ error: "model must be a string or null if provided" }, 400);
|
|
132
|
+
}
|
|
133
|
+
if (isActive !== undefined && typeof isActive !== "boolean") {
|
|
134
|
+
return c.json({ error: "isActive must be a boolean if provided" }, 400);
|
|
135
|
+
}
|
|
136
|
+
if (sortOrder !== undefined && (typeof sortOrder !== "number" || !Number.isInteger(sortOrder))) {
|
|
137
|
+
return c.json({ error: "sortOrder must be an integer if provided" }, 400);
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const adminUser = c.get("user")?.id ?? "unknown";
|
|
141
|
+
const input = {};
|
|
142
|
+
if (capability !== undefined)
|
|
143
|
+
input.capability = capability;
|
|
144
|
+
if (displayName !== undefined)
|
|
145
|
+
input.displayName = displayName;
|
|
146
|
+
if (unit !== undefined)
|
|
147
|
+
input.unit = unit;
|
|
148
|
+
if (priceUsd !== undefined)
|
|
149
|
+
input.priceUsd = priceUsd;
|
|
150
|
+
if ("model" in body)
|
|
151
|
+
input.model = model;
|
|
152
|
+
if (isActive !== undefined)
|
|
153
|
+
input.isActive = isActive;
|
|
154
|
+
if (sortOrder !== undefined)
|
|
155
|
+
input.sortOrder = sortOrder;
|
|
156
|
+
let result;
|
|
157
|
+
try {
|
|
158
|
+
result = await store.updateSellRate(id, input);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
safeAuditLog(auditLogger, {
|
|
162
|
+
adminUser,
|
|
163
|
+
action: "rates.updateSellRate",
|
|
164
|
+
category: "config",
|
|
165
|
+
details: { id, ...input, error: String(err) },
|
|
166
|
+
outcome: "failure",
|
|
167
|
+
});
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
safeAuditLog(auditLogger, {
|
|
171
|
+
adminUser,
|
|
172
|
+
action: "rates.updateSellRate",
|
|
173
|
+
category: "config",
|
|
174
|
+
details: { id, ...input },
|
|
175
|
+
outcome: "success",
|
|
176
|
+
});
|
|
177
|
+
return c.json(result, 200);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (err instanceof Error && err.message.includes("not found")) {
|
|
181
|
+
return c.json({ error: err.message }, 404);
|
|
182
|
+
}
|
|
183
|
+
return c.json({ error: err instanceof Error ? err.message : "Internal server error" }, 500);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
routes.delete("/sell/:id", async (c) => {
|
|
187
|
+
const store = storeFactory();
|
|
188
|
+
const id = c.req.param("id");
|
|
189
|
+
try {
|
|
190
|
+
const adminUser = c.get("user")?.id ?? "unknown";
|
|
191
|
+
let deleted;
|
|
192
|
+
try {
|
|
193
|
+
deleted = await store.deleteSellRate(id);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
safeAuditLog(auditLogger, {
|
|
197
|
+
adminUser,
|
|
198
|
+
action: "rates.deleteSellRate",
|
|
199
|
+
category: "config",
|
|
200
|
+
details: { id, error: String(err) },
|
|
201
|
+
outcome: "failure",
|
|
202
|
+
});
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
if (deleted) {
|
|
206
|
+
safeAuditLog(auditLogger, {
|
|
207
|
+
adminUser,
|
|
208
|
+
action: "rates.deleteSellRate",
|
|
209
|
+
category: "config",
|
|
210
|
+
details: { id },
|
|
211
|
+
outcome: "success",
|
|
212
|
+
});
|
|
213
|
+
return c.json({ success: true }, 200);
|
|
214
|
+
}
|
|
215
|
+
return c.json({ error: "Sell rate not found" }, 404);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
// ── Provider Costs ──
|
|
222
|
+
routes.post("/provider", async (c) => {
|
|
223
|
+
const store = storeFactory();
|
|
224
|
+
let body;
|
|
225
|
+
try {
|
|
226
|
+
body = (await c.req.json());
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
230
|
+
}
|
|
231
|
+
const { capability, adapter, model, unit, costUsd, priority, latencyClass, isActive } = body;
|
|
232
|
+
if (typeof capability !== "string" || !capability.trim()) {
|
|
233
|
+
return c.json({ error: "capability is required and must be non-empty" }, 400);
|
|
234
|
+
}
|
|
235
|
+
if (typeof adapter !== "string" || !adapter.trim()) {
|
|
236
|
+
return c.json({ error: "adapter is required and must be non-empty" }, 400);
|
|
237
|
+
}
|
|
238
|
+
if (typeof unit !== "string" || !unit.trim()) {
|
|
239
|
+
return c.json({ error: "unit is required and must be non-empty" }, 400);
|
|
240
|
+
}
|
|
241
|
+
if (typeof costUsd !== "number" || costUsd <= 0) {
|
|
242
|
+
return c.json({ error: "costUsd must be a positive number" }, 400);
|
|
243
|
+
}
|
|
244
|
+
if (model !== undefined && typeof model !== "string") {
|
|
245
|
+
return c.json({ error: "model must be a string if provided" }, 400);
|
|
246
|
+
}
|
|
247
|
+
if (priority !== undefined && (typeof priority !== "number" || !Number.isInteger(priority))) {
|
|
248
|
+
return c.json({ error: "priority must be an integer if provided" }, 400);
|
|
249
|
+
}
|
|
250
|
+
if (latencyClass !== undefined && typeof latencyClass !== "string") {
|
|
251
|
+
return c.json({ error: "latencyClass must be a string if provided" }, 400);
|
|
252
|
+
}
|
|
253
|
+
if (isActive !== undefined && typeof isActive !== "boolean") {
|
|
254
|
+
return c.json({ error: "isActive must be a boolean if provided" }, 400);
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const adminUser = c.get("user")?.id ?? "unknown";
|
|
258
|
+
const input = {
|
|
259
|
+
capability,
|
|
260
|
+
adapter,
|
|
261
|
+
unit,
|
|
262
|
+
costUsd,
|
|
263
|
+
model: model,
|
|
264
|
+
priority: priority,
|
|
265
|
+
latencyClass: latencyClass,
|
|
266
|
+
isActive: isActive,
|
|
267
|
+
};
|
|
268
|
+
let result;
|
|
269
|
+
try {
|
|
270
|
+
result = await store.createProviderCost(input);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
safeAuditLog(auditLogger, {
|
|
274
|
+
adminUser,
|
|
275
|
+
action: "rates.createProviderCost",
|
|
276
|
+
category: "config",
|
|
277
|
+
details: { ...input, error: String(err) },
|
|
278
|
+
outcome: "failure",
|
|
279
|
+
});
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
safeAuditLog(auditLogger, {
|
|
283
|
+
adminUser,
|
|
284
|
+
action: "rates.createProviderCost",
|
|
285
|
+
category: "config",
|
|
286
|
+
details: { ...input },
|
|
287
|
+
outcome: "success",
|
|
288
|
+
});
|
|
289
|
+
return c.json(result, 201);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
return c.json({ error: err instanceof Error ? err.message : "Internal server error" }, 500);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
routes.put("/provider/:id", async (c) => {
|
|
296
|
+
const store = storeFactory();
|
|
297
|
+
const id = c.req.param("id");
|
|
298
|
+
let body;
|
|
299
|
+
try {
|
|
300
|
+
body = (await c.req.json());
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
304
|
+
}
|
|
305
|
+
const { capability, adapter, model, unit, costUsd, priority, latencyClass, isActive } = body;
|
|
306
|
+
if (capability !== undefined && (typeof capability !== "string" || !capability.trim())) {
|
|
307
|
+
return c.json({ error: "capability must be non-empty if provided" }, 400);
|
|
308
|
+
}
|
|
309
|
+
if (adapter !== undefined && (typeof adapter !== "string" || !adapter.trim())) {
|
|
310
|
+
return c.json({ error: "adapter must be non-empty if provided" }, 400);
|
|
311
|
+
}
|
|
312
|
+
if (unit !== undefined && (typeof unit !== "string" || !unit.trim())) {
|
|
313
|
+
return c.json({ error: "unit must be non-empty if provided" }, 400);
|
|
314
|
+
}
|
|
315
|
+
if (costUsd !== undefined && (typeof costUsd !== "number" || costUsd <= 0)) {
|
|
316
|
+
return c.json({ error: "costUsd must be a positive number if provided" }, 400);
|
|
317
|
+
}
|
|
318
|
+
if (model !== undefined && model !== null && typeof model !== "string") {
|
|
319
|
+
return c.json({ error: "model must be a string or null if provided" }, 400);
|
|
320
|
+
}
|
|
321
|
+
if (priority !== undefined && (typeof priority !== "number" || !Number.isInteger(priority))) {
|
|
322
|
+
return c.json({ error: "priority must be an integer if provided" }, 400);
|
|
323
|
+
}
|
|
324
|
+
if (latencyClass !== undefined && typeof latencyClass !== "string") {
|
|
325
|
+
return c.json({ error: "latencyClass must be a string if provided" }, 400);
|
|
326
|
+
}
|
|
327
|
+
if (isActive !== undefined && typeof isActive !== "boolean") {
|
|
328
|
+
return c.json({ error: "isActive must be a boolean if provided" }, 400);
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const adminUser = c.get("user")?.id ?? "unknown";
|
|
332
|
+
const input = {};
|
|
333
|
+
if (capability !== undefined)
|
|
334
|
+
input.capability = capability;
|
|
335
|
+
if (adapter !== undefined)
|
|
336
|
+
input.adapter = adapter;
|
|
337
|
+
if (unit !== undefined)
|
|
338
|
+
input.unit = unit;
|
|
339
|
+
if (costUsd !== undefined)
|
|
340
|
+
input.costUsd = costUsd;
|
|
341
|
+
if ("model" in body)
|
|
342
|
+
input.model = model;
|
|
343
|
+
if (priority !== undefined)
|
|
344
|
+
input.priority = priority;
|
|
345
|
+
if (latencyClass !== undefined)
|
|
346
|
+
input.latencyClass = latencyClass;
|
|
347
|
+
if (isActive !== undefined)
|
|
348
|
+
input.isActive = isActive;
|
|
349
|
+
let result;
|
|
350
|
+
try {
|
|
351
|
+
result = await store.updateProviderCost(id, input);
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
safeAuditLog(auditLogger, {
|
|
355
|
+
adminUser,
|
|
356
|
+
action: "rates.updateProviderCost",
|
|
357
|
+
category: "config",
|
|
358
|
+
details: { id, ...input, error: String(err) },
|
|
359
|
+
outcome: "failure",
|
|
360
|
+
});
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
safeAuditLog(auditLogger, {
|
|
364
|
+
adminUser,
|
|
365
|
+
action: "rates.updateProviderCost",
|
|
366
|
+
category: "config",
|
|
367
|
+
details: { id, ...input },
|
|
368
|
+
outcome: "success",
|
|
369
|
+
});
|
|
370
|
+
return c.json(result, 200);
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
if (err instanceof Error && err.message.includes("not found")) {
|
|
374
|
+
return c.json({ error: err.message }, 404);
|
|
375
|
+
}
|
|
376
|
+
return c.json({ error: err instanceof Error ? err.message : "Internal server error" }, 500);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
routes.delete("/provider/:id", async (c) => {
|
|
380
|
+
const store = storeFactory();
|
|
381
|
+
const id = c.req.param("id");
|
|
382
|
+
try {
|
|
383
|
+
const adminUser = c.get("user")?.id ?? "unknown";
|
|
384
|
+
let deleted;
|
|
385
|
+
try {
|
|
386
|
+
deleted = await store.deleteProviderCost(id);
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
safeAuditLog(auditLogger, {
|
|
390
|
+
adminUser,
|
|
391
|
+
action: "rates.deleteProviderCost",
|
|
392
|
+
category: "config",
|
|
393
|
+
details: { id, error: String(err) },
|
|
394
|
+
outcome: "failure",
|
|
395
|
+
});
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
if (deleted) {
|
|
399
|
+
safeAuditLog(auditLogger, {
|
|
400
|
+
adminUser,
|
|
401
|
+
action: "rates.deleteProviderCost",
|
|
402
|
+
category: "config",
|
|
403
|
+
details: { id },
|
|
404
|
+
outcome: "success",
|
|
405
|
+
});
|
|
406
|
+
return c.json({ success: true }, 200);
|
|
407
|
+
}
|
|
408
|
+
return c.json({ error: "Provider cost not found" }, 404);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// ── Margin Report ──
|
|
415
|
+
routes.get("/margins", async (c) => {
|
|
416
|
+
const store = storeFactory();
|
|
417
|
+
const capability = c.req.query("capability");
|
|
418
|
+
try {
|
|
419
|
+
const report = await store.getMarginReport(capability);
|
|
420
|
+
return c.json({ margins: report });
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
return routes;
|
|
427
|
+
}
|