@wopr-network/platform-core 1.67.1 → 1.69.0
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/auth/better-auth.js +7 -0
- package/dist/backup/types.d.ts +1 -1
- package/dist/email/client.js +16 -0
- package/dist/server/__tests__/build-container.test.d.ts +1 -0
- package/dist/server/__tests__/build-container.test.js +339 -0
- package/dist/server/__tests__/container.test.d.ts +1 -0
- package/dist/server/__tests__/container.test.js +170 -0
- package/dist/server/__tests__/lifecycle.test.d.ts +1 -0
- package/dist/server/__tests__/lifecycle.test.js +90 -0
- package/dist/server/__tests__/mount-routes.test.d.ts +1 -0
- package/dist/server/__tests__/mount-routes.test.js +151 -0
- package/dist/server/boot-config.d.ts +51 -0
- package/dist/server/boot-config.js +7 -0
- package/dist/server/container.d.ts +81 -0
- package/dist/server/container.js +134 -0
- package/dist/server/index.d.ts +33 -0
- package/dist/server/index.js +66 -0
- package/dist/server/lifecycle.d.ts +25 -0
- package/dist/server/lifecycle.js +46 -0
- package/dist/server/middleware/__tests__/admin-auth.test.d.ts +1 -0
- package/dist/server/middleware/__tests__/admin-auth.test.js +59 -0
- package/dist/server/middleware/__tests__/tenant-proxy.test.d.ts +1 -0
- package/dist/server/middleware/__tests__/tenant-proxy.test.js +268 -0
- package/dist/server/middleware/admin-auth.d.ts +18 -0
- package/dist/server/middleware/admin-auth.js +38 -0
- package/dist/server/middleware/tenant-proxy.d.ts +56 -0
- package/dist/server/middleware/tenant-proxy.js +162 -0
- package/dist/server/mount-routes.d.ts +30 -0
- package/dist/server/mount-routes.js +74 -0
- package/dist/server/routes/__tests__/admin.test.d.ts +1 -0
- package/dist/server/routes/__tests__/admin.test.js +267 -0
- package/dist/server/routes/__tests__/crypto-webhook.test.d.ts +1 -0
- package/dist/server/routes/__tests__/crypto-webhook.test.js +137 -0
- package/dist/server/routes/__tests__/provision-webhook.test.d.ts +1 -0
- package/dist/server/routes/__tests__/provision-webhook.test.js +212 -0
- package/dist/server/routes/__tests__/stripe-webhook.test.d.ts +1 -0
- package/dist/server/routes/__tests__/stripe-webhook.test.js +65 -0
- package/dist/server/routes/admin.d.ts +111 -0
- package/dist/server/routes/admin.js +273 -0
- package/dist/server/routes/crypto-webhook.d.ts +23 -0
- package/dist/server/routes/crypto-webhook.js +82 -0
- package/dist/server/routes/provision-webhook.d.ts +38 -0
- package/dist/server/routes/provision-webhook.js +160 -0
- package/dist/server/routes/stripe-webhook.d.ts +10 -0
- package/dist/server/routes/stripe-webhook.js +29 -0
- package/dist/server/test-container.d.ts +15 -0
- package/dist/server/test-container.js +103 -0
- package/dist/trpc/auth-helpers.d.ts +17 -0
- package/dist/trpc/auth-helpers.js +26 -0
- package/dist/trpc/container-factories.d.ts +300 -0
- package/dist/trpc/container-factories.js +80 -0
- package/dist/trpc/index.d.ts +2 -0
- package/dist/trpc/index.js +2 -0
- package/package.json +8 -3
- package/src/auth/better-auth.ts +8 -0
- package/src/email/client.ts +18 -0
- package/src/server/__tests__/build-container.test.ts +402 -0
- package/src/server/__tests__/container.test.ts +204 -0
- package/src/server/__tests__/lifecycle.test.ts +106 -0
- package/src/server/__tests__/mount-routes.test.ts +169 -0
- package/src/server/boot-config.ts +84 -0
- package/src/server/container.ts +237 -0
- package/src/server/index.ts +92 -0
- package/src/server/lifecycle.ts +62 -0
- package/src/server/middleware/__tests__/admin-auth.test.ts +67 -0
- package/src/server/middleware/__tests__/tenant-proxy.test.ts +308 -0
- package/src/server/middleware/admin-auth.ts +51 -0
- package/src/server/middleware/tenant-proxy.ts +192 -0
- package/src/server/mount-routes.ts +113 -0
- package/src/server/routes/__tests__/admin.test.ts +320 -0
- package/src/server/routes/__tests__/crypto-webhook.test.ts +167 -0
- package/src/server/routes/__tests__/provision-webhook.test.ts +323 -0
- package/src/server/routes/__tests__/stripe-webhook.test.ts +73 -0
- package/src/server/routes/admin.ts +334 -0
- package/src/server/routes/crypto-webhook.ts +110 -0
- package/src/server/routes/provision-webhook.ts +212 -0
- package/src/server/routes/stripe-webhook.ts +36 -0
- package/src/server/test-container.ts +120 -0
- package/src/trpc/auth-helpers.ts +28 -0
- package/src/trpc/container-factories.ts +114 -0
- package/src/trpc/index.ts +9 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provision webhook routes — instance lifecycle management.
|
|
3
|
+
*
|
|
4
|
+
* POST /create — spin up a new container and configure it
|
|
5
|
+
* POST /destroy — tear down a container
|
|
6
|
+
* PUT /budget — update a container's spending budget
|
|
7
|
+
*
|
|
8
|
+
* Extracted from product-specific implementations into platform-core so
|
|
9
|
+
* every product gets the same timing-safe auth and DI-based fleet access
|
|
10
|
+
* without copy-pasting.
|
|
11
|
+
*
|
|
12
|
+
* All env var names are generic (no product-specific prefixes).
|
|
13
|
+
*/
|
|
14
|
+
import { timingSafeEqual } from "node:crypto";
|
|
15
|
+
import { Hono } from "hono";
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Timing-safe secret validation (same pattern as crypto-webhook)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
function assertSecret(authHeader, secret) {
|
|
20
|
+
if (!authHeader?.startsWith("Bearer "))
|
|
21
|
+
return false;
|
|
22
|
+
const token = authHeader.slice("Bearer ".length).trim();
|
|
23
|
+
if (token.length !== secret.length)
|
|
24
|
+
return false;
|
|
25
|
+
return timingSafeEqual(Buffer.from(token), Buffer.from(secret));
|
|
26
|
+
}
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Route factory
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Create the provision webhook Hono sub-app.
|
|
32
|
+
*
|
|
33
|
+
* Mount it at `/api/provision` (or wherever the product prefers).
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* app.route("/api/provision", createProvisionWebhookRoutes(container, config));
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function createProvisionWebhookRoutes(container, config) {
|
|
40
|
+
const app = new Hono();
|
|
41
|
+
// ------------------------------------------------------------------
|
|
42
|
+
// POST /create — create a new managed instance
|
|
43
|
+
// ------------------------------------------------------------------
|
|
44
|
+
app.post("/create", async (c) => {
|
|
45
|
+
if (!assertSecret(c.req.header("authorization"), config.provisionSecret)) {
|
|
46
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
47
|
+
}
|
|
48
|
+
if (!container.fleet) {
|
|
49
|
+
return c.json({ error: "Fleet management not configured" }, 501);
|
|
50
|
+
}
|
|
51
|
+
const body = await c.req.json();
|
|
52
|
+
const { tenantId, subdomain } = body;
|
|
53
|
+
if (!tenantId || !subdomain) {
|
|
54
|
+
return c.json({ error: "Missing required fields: tenantId, subdomain" }, 422);
|
|
55
|
+
}
|
|
56
|
+
// Billing gate — require positive credit balance before provisioning
|
|
57
|
+
const balance = await container.creditLedger.balance(tenantId);
|
|
58
|
+
if (typeof balance === "object" && "isZero" in balance) {
|
|
59
|
+
const bal = balance;
|
|
60
|
+
if (bal.isZero() || bal.isNegative()) {
|
|
61
|
+
return c.json({ error: "Insufficient credits: add funds before creating an instance" }, 402);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Instance limit gate
|
|
65
|
+
const { profileStore, manager: fleet, proxy } = container.fleet;
|
|
66
|
+
if (config.maxInstancesPerTenant > 0) {
|
|
67
|
+
const profiles = await profileStore.list();
|
|
68
|
+
const tenantInstances = profiles.filter((p) => p.tenantId === tenantId);
|
|
69
|
+
if (tenantInstances.length >= config.maxInstancesPerTenant) {
|
|
70
|
+
return c.json({ error: `Instance limit reached: maximum ${config.maxInstancesPerTenant} per tenant` }, 403);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Create the Docker container
|
|
74
|
+
const instance = await fleet.create({
|
|
75
|
+
tenantId,
|
|
76
|
+
name: subdomain,
|
|
77
|
+
description: `Managed instance for ${subdomain}`,
|
|
78
|
+
image: config.instanceImage,
|
|
79
|
+
env: {
|
|
80
|
+
PORT: String(config.containerPort),
|
|
81
|
+
PROVISION_SECRET: config.provisionSecret,
|
|
82
|
+
HOSTED_MODE: "true",
|
|
83
|
+
DEPLOYMENT_MODE: "hosted_proxy",
|
|
84
|
+
DEPLOYMENT_EXPOSURE: "private",
|
|
85
|
+
MIGRATION_AUTO_APPLY: "true",
|
|
86
|
+
},
|
|
87
|
+
restartPolicy: "unless-stopped",
|
|
88
|
+
releaseChannel: "stable",
|
|
89
|
+
updatePolicy: "manual",
|
|
90
|
+
});
|
|
91
|
+
// Register proxy route — container name must match FleetManager naming convention
|
|
92
|
+
const prefix = config.containerPrefix ?? "wopr";
|
|
93
|
+
const containerName = `${prefix}-${subdomain}`;
|
|
94
|
+
await proxy.addRoute({
|
|
95
|
+
instanceId: instance.id,
|
|
96
|
+
subdomain,
|
|
97
|
+
upstreamHost: containerName,
|
|
98
|
+
upstreamPort: config.containerPort,
|
|
99
|
+
healthy: true,
|
|
100
|
+
});
|
|
101
|
+
return c.json({
|
|
102
|
+
ok: true,
|
|
103
|
+
instanceId: instance.id,
|
|
104
|
+
subdomain,
|
|
105
|
+
containerUrl: `http://${containerName}:${config.containerPort}`,
|
|
106
|
+
}, 201);
|
|
107
|
+
});
|
|
108
|
+
// ------------------------------------------------------------------
|
|
109
|
+
// POST /destroy — tear down a managed instance
|
|
110
|
+
// ------------------------------------------------------------------
|
|
111
|
+
app.post("/destroy", async (c) => {
|
|
112
|
+
if (!assertSecret(c.req.header("authorization"), config.provisionSecret)) {
|
|
113
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
114
|
+
}
|
|
115
|
+
if (!container.fleet) {
|
|
116
|
+
return c.json({ error: "Fleet management not configured" }, 501);
|
|
117
|
+
}
|
|
118
|
+
const body = await c.req.json();
|
|
119
|
+
const { instanceId } = body;
|
|
120
|
+
if (!instanceId) {
|
|
121
|
+
return c.json({ error: "Missing required field: instanceId" }, 422);
|
|
122
|
+
}
|
|
123
|
+
const { manager: fleet, proxy, serviceKeyRepo } = container.fleet;
|
|
124
|
+
// Revoke gateway service key
|
|
125
|
+
await serviceKeyRepo.revokeByInstance(instanceId);
|
|
126
|
+
// Remove the Docker container
|
|
127
|
+
try {
|
|
128
|
+
await fleet.remove(instanceId);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Container may already be gone — continue cleanup
|
|
132
|
+
}
|
|
133
|
+
// Remove proxy route
|
|
134
|
+
proxy.removeRoute(instanceId);
|
|
135
|
+
return c.json({ ok: true });
|
|
136
|
+
});
|
|
137
|
+
// ------------------------------------------------------------------
|
|
138
|
+
// PUT /budget — update a container's spending budget
|
|
139
|
+
// ------------------------------------------------------------------
|
|
140
|
+
app.put("/budget", async (c) => {
|
|
141
|
+
if (!assertSecret(c.req.header("authorization"), config.provisionSecret)) {
|
|
142
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
143
|
+
}
|
|
144
|
+
if (!container.fleet) {
|
|
145
|
+
return c.json({ error: "Fleet management not configured" }, 501);
|
|
146
|
+
}
|
|
147
|
+
const body = await c.req.json();
|
|
148
|
+
const { instanceId, tenantEntityId, budgetCents } = body;
|
|
149
|
+
if (!instanceId || !tenantEntityId || budgetCents === undefined) {
|
|
150
|
+
return c.json({ error: "Missing required fields: instanceId, tenantEntityId, budgetCents" }, 422);
|
|
151
|
+
}
|
|
152
|
+
const { manager: fleet } = container.fleet;
|
|
153
|
+
const status = await fleet.status(instanceId);
|
|
154
|
+
if (status.state !== "running") {
|
|
155
|
+
return c.json({ error: "Instance not running" }, 503);
|
|
156
|
+
}
|
|
157
|
+
return c.json({ ok: true, instanceId, budgetCents });
|
|
158
|
+
});
|
|
159
|
+
return app;
|
|
160
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { PlatformContainer } from "../container.js";
|
|
3
|
+
/**
|
|
4
|
+
* Stripe webhook route factory.
|
|
5
|
+
*
|
|
6
|
+
* Delegates to `container.stripe.processor.handleWebhook()` which
|
|
7
|
+
* calls `stripe.webhooks.constructEvent()` internally for signature
|
|
8
|
+
* verification.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createStripeWebhookRoutes(container: PlatformContainer): Hono;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
/**
|
|
3
|
+
* Stripe webhook route factory.
|
|
4
|
+
*
|
|
5
|
+
* Delegates to `container.stripe.processor.handleWebhook()` which
|
|
6
|
+
* calls `stripe.webhooks.constructEvent()` internally for signature
|
|
7
|
+
* verification.
|
|
8
|
+
*/
|
|
9
|
+
export function createStripeWebhookRoutes(container) {
|
|
10
|
+
const routes = new Hono();
|
|
11
|
+
routes.post("/", async (c) => {
|
|
12
|
+
if (!container.stripe) {
|
|
13
|
+
return c.json({ error: "Stripe not configured" }, 501);
|
|
14
|
+
}
|
|
15
|
+
const rawBody = Buffer.from(await c.req.arrayBuffer());
|
|
16
|
+
const sig = c.req.header("stripe-signature");
|
|
17
|
+
if (!sig) {
|
|
18
|
+
return c.json({ error: "Missing stripe-signature header" }, 400);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const result = await container.stripe.processor.handleWebhook(rawBody, sig);
|
|
22
|
+
return c.json({ ok: true, result }, 200);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return c.json({ error: "Webhook processing failed" }, 400);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return routes;
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createTestContainer — builds a PlatformContainer with sensible mock
|
|
3
|
+
* defaults for unit tests. All feature sub-containers default to null.
|
|
4
|
+
* Core services get minimal stubs that satisfy their interfaces.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const c = createTestContainer();
|
|
8
|
+
* const c2 = createTestContainer({ creditLedger: myCustomLedger });
|
|
9
|
+
*/
|
|
10
|
+
import type { PlatformContainer } from "./container.js";
|
|
11
|
+
/**
|
|
12
|
+
* Create a PlatformContainer pre-filled with no-op stubs.
|
|
13
|
+
* Pass overrides for any field you need to customize in your test.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createTestContainer(overrides?: Partial<PlatformContainer>): PlatformContainer;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createTestContainer — builds a PlatformContainer with sensible mock
|
|
3
|
+
* defaults for unit tests. All feature sub-containers default to null.
|
|
4
|
+
* Core services get minimal stubs that satisfy their interfaces.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const c = createTestContainer();
|
|
8
|
+
* const c2 = createTestContainer({ creditLedger: myCustomLedger });
|
|
9
|
+
*/
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Stub factories (satisfy interface contracts with no-op implementations)
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
function stubLedger() {
|
|
14
|
+
const zero = 0;
|
|
15
|
+
const emptyEntry = {};
|
|
16
|
+
return {
|
|
17
|
+
post: async () => emptyEntry,
|
|
18
|
+
credit: async () => emptyEntry,
|
|
19
|
+
debit: async () => emptyEntry,
|
|
20
|
+
balance: async () => zero,
|
|
21
|
+
hasReferenceId: async () => false,
|
|
22
|
+
history: async () => [],
|
|
23
|
+
tenantsWithBalance: async () => [],
|
|
24
|
+
memberUsage: async () => [],
|
|
25
|
+
lifetimeSpend: async () => zero,
|
|
26
|
+
lifetimeSpendBatch: async () => new Map(),
|
|
27
|
+
expiredCredits: async () => [],
|
|
28
|
+
trialBalance: async () => ({ balanced: true }),
|
|
29
|
+
accountBalance: async () => zero,
|
|
30
|
+
seedSystemAccounts: async () => { },
|
|
31
|
+
existsByReferenceIdLike: async () => false,
|
|
32
|
+
sumPurchasesForPeriod: async () => zero,
|
|
33
|
+
getActiveTenantIdsInWindow: async () => [],
|
|
34
|
+
debitCapped: async () => null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function stubOrgMemberRepo() {
|
|
38
|
+
return {
|
|
39
|
+
listMembers: async () => [],
|
|
40
|
+
addMember: async () => { },
|
|
41
|
+
updateMemberRole: async () => { },
|
|
42
|
+
removeMember: async () => { },
|
|
43
|
+
findMember: async () => null,
|
|
44
|
+
countAdminsAndOwners: async () => 0,
|
|
45
|
+
listInvites: async () => [],
|
|
46
|
+
createInvite: async () => { },
|
|
47
|
+
findInviteById: async () => null,
|
|
48
|
+
findInviteByToken: async () => null,
|
|
49
|
+
deleteInvite: async () => { },
|
|
50
|
+
deleteAllMembers: async () => { },
|
|
51
|
+
deleteAllInvites: async () => { },
|
|
52
|
+
listOrgsByUser: async () => [],
|
|
53
|
+
markInviteAccepted: async () => { },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function stubUserRoleRepo() {
|
|
57
|
+
return {
|
|
58
|
+
getTenantIdByUserId: async () => null,
|
|
59
|
+
grantRole: async () => { },
|
|
60
|
+
revokeRole: async () => false,
|
|
61
|
+
listRolesByUser: async () => [],
|
|
62
|
+
listUsersByRole: async () => [],
|
|
63
|
+
isPlatformAdmin: async () => false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function stubProductConfig() {
|
|
67
|
+
return {
|
|
68
|
+
product: {
|
|
69
|
+
slug: "test",
|
|
70
|
+
name: "Test Product",
|
|
71
|
+
},
|
|
72
|
+
navItems: [],
|
|
73
|
+
domains: [],
|
|
74
|
+
features: null,
|
|
75
|
+
fleet: null,
|
|
76
|
+
billing: null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Public API
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Create a PlatformContainer pre-filled with no-op stubs.
|
|
84
|
+
* Pass overrides for any field you need to customize in your test.
|
|
85
|
+
*/
|
|
86
|
+
export function createTestContainer(overrides) {
|
|
87
|
+
const defaults = {
|
|
88
|
+
db: {},
|
|
89
|
+
pool: { end: async () => { } },
|
|
90
|
+
productConfig: stubProductConfig(),
|
|
91
|
+
creditLedger: stubLedger(),
|
|
92
|
+
orgMemberRepo: stubOrgMemberRepo(),
|
|
93
|
+
orgService: {},
|
|
94
|
+
userRoleRepo: stubUserRoleRepo(),
|
|
95
|
+
// Feature sub-containers default to null (not enabled)
|
|
96
|
+
fleet: null,
|
|
97
|
+
crypto: null,
|
|
98
|
+
stripe: null,
|
|
99
|
+
gateway: null,
|
|
100
|
+
hotPool: null,
|
|
101
|
+
};
|
|
102
|
+
return { ...defaults, ...overrides };
|
|
103
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared auth helper factories for tRPC routers.
|
|
3
|
+
*
|
|
4
|
+
* These helpers can be constructed with explicit deps (for container-based DI)
|
|
5
|
+
* instead of relying on module-level singletons.
|
|
6
|
+
*/
|
|
7
|
+
import type { IOrgMemberRepository } from "../tenancy/org-member-repository.js";
|
|
8
|
+
/**
|
|
9
|
+
* Creates an assertOrgAdminOrOwner function closed over the given repository.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```ts
|
|
13
|
+
* const assertOrgAdmin = createAssertOrgAdminOrOwner(container.orgMemberRepo);
|
|
14
|
+
* await assertOrgAdmin(tenantId, userId);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function createAssertOrgAdminOrOwner(orgMemberRepo: IOrgMemberRepository): (tenantId: string, userId: string) => Promise<void>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared auth helper factories for tRPC routers.
|
|
3
|
+
*
|
|
4
|
+
* These helpers can be constructed with explicit deps (for container-based DI)
|
|
5
|
+
* instead of relying on module-level singletons.
|
|
6
|
+
*/
|
|
7
|
+
import { TRPCError } from "@trpc/server";
|
|
8
|
+
/**
|
|
9
|
+
* Creates an assertOrgAdminOrOwner function closed over the given repository.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```ts
|
|
13
|
+
* const assertOrgAdmin = createAssertOrgAdminOrOwner(container.orgMemberRepo);
|
|
14
|
+
* await assertOrgAdmin(tenantId, userId);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function createAssertOrgAdminOrOwner(orgMemberRepo) {
|
|
18
|
+
return async function assertOrgAdminOrOwner(tenantId, userId) {
|
|
19
|
+
if (tenantId === userId)
|
|
20
|
+
return;
|
|
21
|
+
const member = await orgMemberRepo.findMember(tenantId, userId);
|
|
22
|
+
if (!member || (member.role !== "owner" && member.role !== "admin")) {
|
|
23
|
+
throw new TRPCError({ code: "FORBIDDEN", message: "Organization admin access required" });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container-based tRPC router factories.
|
|
3
|
+
*
|
|
4
|
+
* Each function accepts a PlatformContainer (plus any extra deps not yet on
|
|
5
|
+
* the container) and returns a tRPC router by delegating to the existing
|
|
6
|
+
* factory functions. This provides a single-call DI entry point for products
|
|
7
|
+
* that have migrated to the container pattern, while the existing factory
|
|
8
|
+
* functions and setter-based API remain fully functional for products that
|
|
9
|
+
* haven't migrated yet.
|
|
10
|
+
*/
|
|
11
|
+
import type { INotificationTemplateRepository } from "../email/notification-template-repository.js";
|
|
12
|
+
import type { RolloutOrchestrator } from "../fleet/rollout-orchestrator.js";
|
|
13
|
+
import type { ITenantUpdateConfigRepository } from "../fleet/tenant-update-config-repository.js";
|
|
14
|
+
import type { ProductConfigService } from "../product-config/service.js";
|
|
15
|
+
import type { PlatformContainer } from "../server/container.js";
|
|
16
|
+
import { type OrgRemovePaymentMethodDeps } from "./org-remove-payment-method-router.js";
|
|
17
|
+
/**
|
|
18
|
+
* Wire the PlatformContainer's orgMemberRepo into the tRPC middleware layer.
|
|
19
|
+
*
|
|
20
|
+
* This replaces the manual `setTrpcOrgMemberRepo()` call. Products using the
|
|
21
|
+
* container call this once at boot; the setter-based API remains for products
|
|
22
|
+
* that haven't migrated yet.
|
|
23
|
+
*/
|
|
24
|
+
export declare function initTrpcFromContainer(container: PlatformContainer): void;
|
|
25
|
+
/**
|
|
26
|
+
* Create the admin fleet-update router from a container.
|
|
27
|
+
*
|
|
28
|
+
* Requires additional fleet-specific deps (orchestrator + config repo) that
|
|
29
|
+
* are constructed at boot when fleet is enabled and are not yet on the
|
|
30
|
+
* PlatformContainer itself.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createAdminFleetUpdateRouterFromContainer(_container: PlatformContainer, getOrchestrator: () => RolloutOrchestrator, getConfigRepo: () => ITenantUpdateConfigRepository): import("@trpc/server").TRPCBuiltRouter<{
|
|
33
|
+
ctx: import("./init.js").TRPCContext;
|
|
34
|
+
meta: object;
|
|
35
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
36
|
+
transformer: false;
|
|
37
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
38
|
+
rolloutStatus: import("@trpc/server").TRPCQueryProcedure<{
|
|
39
|
+
input: void;
|
|
40
|
+
output: {
|
|
41
|
+
isRolling: boolean;
|
|
42
|
+
};
|
|
43
|
+
meta: object;
|
|
44
|
+
}>;
|
|
45
|
+
forceRollout: import("@trpc/server").TRPCMutationProcedure<{
|
|
46
|
+
input: void;
|
|
47
|
+
output: {
|
|
48
|
+
triggered: boolean;
|
|
49
|
+
};
|
|
50
|
+
meta: object;
|
|
51
|
+
}>;
|
|
52
|
+
listTenantConfigs: import("@trpc/server").TRPCQueryProcedure<{
|
|
53
|
+
input: void;
|
|
54
|
+
output: import("../fleet/tenant-update-config-repository.js").TenantUpdateConfig[];
|
|
55
|
+
meta: object;
|
|
56
|
+
}>;
|
|
57
|
+
setTenantConfig: import("@trpc/server").TRPCMutationProcedure<{
|
|
58
|
+
input: {
|
|
59
|
+
tenantId: string;
|
|
60
|
+
mode: "manual" | "auto";
|
|
61
|
+
preferredHourUtc?: number | undefined;
|
|
62
|
+
};
|
|
63
|
+
output: void;
|
|
64
|
+
meta: object;
|
|
65
|
+
}>;
|
|
66
|
+
}>>;
|
|
67
|
+
/**
|
|
68
|
+
* Create the fleet-update-config router from a container.
|
|
69
|
+
*
|
|
70
|
+
* Requires the tenant-update-config repository getter (fleet-specific dep
|
|
71
|
+
* not yet on PlatformContainer).
|
|
72
|
+
*/
|
|
73
|
+
export declare function createFleetUpdateConfigRouterFromContainer(_container: PlatformContainer, getConfigRepo: () => ITenantUpdateConfigRepository): import("@trpc/server").TRPCBuiltRouter<{
|
|
74
|
+
ctx: import("./init.js").TRPCContext;
|
|
75
|
+
meta: object;
|
|
76
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
77
|
+
transformer: false;
|
|
78
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
79
|
+
getUpdateConfig: import("@trpc/server").TRPCQueryProcedure<{
|
|
80
|
+
input: {
|
|
81
|
+
tenantId: string;
|
|
82
|
+
};
|
|
83
|
+
output: import("../fleet/tenant-update-config-repository.js").TenantUpdateConfig | null;
|
|
84
|
+
meta: object;
|
|
85
|
+
}>;
|
|
86
|
+
setUpdateConfig: import("@trpc/server").TRPCMutationProcedure<{
|
|
87
|
+
input: {
|
|
88
|
+
tenantId: string;
|
|
89
|
+
mode: "manual" | "auto";
|
|
90
|
+
preferredHourUtc?: number | undefined;
|
|
91
|
+
};
|
|
92
|
+
output: void;
|
|
93
|
+
meta: object;
|
|
94
|
+
}>;
|
|
95
|
+
}>>;
|
|
96
|
+
/**
|
|
97
|
+
* Create the notification-template router from a container.
|
|
98
|
+
*
|
|
99
|
+
* Requires a getter for the notification-template repository (not yet on
|
|
100
|
+
* PlatformContainer).
|
|
101
|
+
*/
|
|
102
|
+
export declare function createNotificationTemplateRouterFromContainer(_container: PlatformContainer, getRepo: () => INotificationTemplateRepository): import("@trpc/server").TRPCBuiltRouter<{
|
|
103
|
+
ctx: import("./init.js").TRPCContext;
|
|
104
|
+
meta: object;
|
|
105
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
106
|
+
transformer: false;
|
|
107
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
108
|
+
listTemplates: import("@trpc/server").TRPCQueryProcedure<{
|
|
109
|
+
input: void;
|
|
110
|
+
output: import("../email/notification-repository-types.js").NotificationTemplateRow[];
|
|
111
|
+
meta: object;
|
|
112
|
+
}>;
|
|
113
|
+
getTemplate: import("@trpc/server").TRPCQueryProcedure<{
|
|
114
|
+
input: {
|
|
115
|
+
name: string;
|
|
116
|
+
};
|
|
117
|
+
output: import("../email/notification-repository-types.js").NotificationTemplateRow | null;
|
|
118
|
+
meta: object;
|
|
119
|
+
}>;
|
|
120
|
+
updateTemplate: import("@trpc/server").TRPCMutationProcedure<{
|
|
121
|
+
input: {
|
|
122
|
+
name: string;
|
|
123
|
+
subject?: string | undefined;
|
|
124
|
+
htmlBody?: string | undefined;
|
|
125
|
+
textBody?: string | undefined;
|
|
126
|
+
description?: string | undefined;
|
|
127
|
+
active?: boolean | undefined;
|
|
128
|
+
};
|
|
129
|
+
output: void;
|
|
130
|
+
meta: object;
|
|
131
|
+
}>;
|
|
132
|
+
previewTemplate: import("@trpc/server").TRPCMutationProcedure<{
|
|
133
|
+
input: {
|
|
134
|
+
subject: string;
|
|
135
|
+
htmlBody: string;
|
|
136
|
+
textBody: string;
|
|
137
|
+
data: Record<string, unknown>;
|
|
138
|
+
};
|
|
139
|
+
output: {
|
|
140
|
+
subject: string;
|
|
141
|
+
html: string;
|
|
142
|
+
text: string;
|
|
143
|
+
};
|
|
144
|
+
meta: object;
|
|
145
|
+
}>;
|
|
146
|
+
seedDefaults: import("@trpc/server").TRPCMutationProcedure<{
|
|
147
|
+
input: void;
|
|
148
|
+
output: {
|
|
149
|
+
inserted: number;
|
|
150
|
+
total: number;
|
|
151
|
+
};
|
|
152
|
+
meta: object;
|
|
153
|
+
}>;
|
|
154
|
+
}>>;
|
|
155
|
+
/**
|
|
156
|
+
* Create the org-remove-payment-method router from a container.
|
|
157
|
+
*
|
|
158
|
+
* The container's StripeServices.processor is typed narrowly (webhook-only),
|
|
159
|
+
* so this still requires the full OrgRemovePaymentMethodDeps to be supplied
|
|
160
|
+
* until the container's Stripe sub-container is widened to include
|
|
161
|
+
* IPaymentProcessor.
|
|
162
|
+
*/
|
|
163
|
+
export declare function createOrgRemovePaymentMethodRouterFromContainer(_container: PlatformContainer, getDeps: () => OrgRemovePaymentMethodDeps): import("@trpc/server").TRPCBuiltRouter<{
|
|
164
|
+
ctx: import("./init.js").TRPCContext;
|
|
165
|
+
meta: object;
|
|
166
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
167
|
+
transformer: false;
|
|
168
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
169
|
+
orgRemovePaymentMethod: import("@trpc/server").TRPCMutationProcedure<{
|
|
170
|
+
input: {
|
|
171
|
+
orgId: string;
|
|
172
|
+
paymentMethodId: string;
|
|
173
|
+
};
|
|
174
|
+
output: {
|
|
175
|
+
removed: boolean;
|
|
176
|
+
};
|
|
177
|
+
meta: object;
|
|
178
|
+
}>;
|
|
179
|
+
}>>;
|
|
180
|
+
/**
|
|
181
|
+
* Create the product-config router from a container.
|
|
182
|
+
*
|
|
183
|
+
* Requires a getter for ProductConfigService and the product slug, both of
|
|
184
|
+
* which are product-specific and not on PlatformContainer.
|
|
185
|
+
*/
|
|
186
|
+
export declare function createProductConfigRouterFromContainer(_container: PlatformContainer, getService: () => ProductConfigService, productSlug: string): import("@trpc/server").TRPCBuiltRouter<{
|
|
187
|
+
ctx: import("./init.js").TRPCContext;
|
|
188
|
+
meta: object;
|
|
189
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
190
|
+
transformer: false;
|
|
191
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
192
|
+
getBrandConfig: import("@trpc/server").TRPCQueryProcedure<{
|
|
193
|
+
input: void;
|
|
194
|
+
output: import("../product-config/repository-types.js").ProductBrandConfig | null;
|
|
195
|
+
meta: object;
|
|
196
|
+
}>;
|
|
197
|
+
getNavItems: import("@trpc/server").TRPCQueryProcedure<{
|
|
198
|
+
input: void;
|
|
199
|
+
output: {
|
|
200
|
+
label: string;
|
|
201
|
+
href: string;
|
|
202
|
+
}[];
|
|
203
|
+
meta: object;
|
|
204
|
+
}>;
|
|
205
|
+
admin: import("@trpc/server").TRPCBuiltRouter<{
|
|
206
|
+
ctx: import("./init.js").TRPCContext;
|
|
207
|
+
meta: object;
|
|
208
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
209
|
+
transformer: false;
|
|
210
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
211
|
+
get: import("@trpc/server").TRPCQueryProcedure<{
|
|
212
|
+
input: void;
|
|
213
|
+
output: import("../product-config/repository-types.js").ProductConfig | null;
|
|
214
|
+
meta: object;
|
|
215
|
+
}>;
|
|
216
|
+
listAll: import("@trpc/server").TRPCQueryProcedure<{
|
|
217
|
+
input: void;
|
|
218
|
+
output: import("../product-config/repository-types.js").ProductConfig[];
|
|
219
|
+
meta: object;
|
|
220
|
+
}>;
|
|
221
|
+
updateBrand: import("@trpc/server").TRPCMutationProcedure<{
|
|
222
|
+
input: {
|
|
223
|
+
brandName?: string | undefined;
|
|
224
|
+
productName?: string | undefined;
|
|
225
|
+
tagline?: string | undefined;
|
|
226
|
+
domain?: string | undefined;
|
|
227
|
+
appDomain?: string | undefined;
|
|
228
|
+
cookieDomain?: string | undefined;
|
|
229
|
+
companyLegal?: string | undefined;
|
|
230
|
+
priceLabel?: string | undefined;
|
|
231
|
+
defaultImage?: string | undefined;
|
|
232
|
+
emailSupport?: string | undefined;
|
|
233
|
+
emailPrivacy?: string | undefined;
|
|
234
|
+
emailLegal?: string | undefined;
|
|
235
|
+
fromEmail?: string | undefined;
|
|
236
|
+
homePath?: string | undefined;
|
|
237
|
+
storagePrefix?: string | undefined;
|
|
238
|
+
};
|
|
239
|
+
output: void;
|
|
240
|
+
meta: object;
|
|
241
|
+
}>;
|
|
242
|
+
updateNavItems: import("@trpc/server").TRPCMutationProcedure<{
|
|
243
|
+
input: {
|
|
244
|
+
label: string;
|
|
245
|
+
href: string;
|
|
246
|
+
sortOrder: number;
|
|
247
|
+
icon?: string | undefined;
|
|
248
|
+
requiresRole?: string | undefined;
|
|
249
|
+
enabled?: boolean | undefined;
|
|
250
|
+
}[];
|
|
251
|
+
output: void;
|
|
252
|
+
meta: object;
|
|
253
|
+
}>;
|
|
254
|
+
updateFeatures: import("@trpc/server").TRPCMutationProcedure<{
|
|
255
|
+
input: {
|
|
256
|
+
chatEnabled?: boolean | undefined;
|
|
257
|
+
onboardingEnabled?: boolean | undefined;
|
|
258
|
+
onboardingDefaultModel?: string | undefined;
|
|
259
|
+
onboardingSystemPrompt?: string | undefined;
|
|
260
|
+
onboardingMaxCredits?: number | undefined;
|
|
261
|
+
onboardingWelcomeMsg?: string | undefined;
|
|
262
|
+
sharedModuleBilling?: boolean | undefined;
|
|
263
|
+
sharedModuleMonitoring?: boolean | undefined;
|
|
264
|
+
sharedModuleAnalytics?: boolean | undefined;
|
|
265
|
+
};
|
|
266
|
+
output: void;
|
|
267
|
+
meta: object;
|
|
268
|
+
}>;
|
|
269
|
+
updateFleet: import("@trpc/server").TRPCMutationProcedure<{
|
|
270
|
+
input: {
|
|
271
|
+
containerImage?: string | undefined;
|
|
272
|
+
containerPort?: number | undefined;
|
|
273
|
+
lifecycle?: "managed" | "ephemeral" | undefined;
|
|
274
|
+
billingModel?: "monthly" | "per_use" | "none" | undefined;
|
|
275
|
+
maxInstances?: number | undefined;
|
|
276
|
+
imageAllowlist?: string[] | undefined;
|
|
277
|
+
dockerNetwork?: string | undefined;
|
|
278
|
+
placementStrategy?: string | undefined;
|
|
279
|
+
fleetDataDir?: string | undefined;
|
|
280
|
+
};
|
|
281
|
+
output: void;
|
|
282
|
+
meta: object;
|
|
283
|
+
}>;
|
|
284
|
+
updateBilling: import("@trpc/server").TRPCMutationProcedure<{
|
|
285
|
+
input: {
|
|
286
|
+
stripePublishableKey?: string | undefined;
|
|
287
|
+
stripeSecretKey?: string | undefined;
|
|
288
|
+
stripeWebhookSecret?: string | undefined;
|
|
289
|
+
creditPrices?: Record<string, number> | undefined;
|
|
290
|
+
affiliateBaseUrl?: string | undefined;
|
|
291
|
+
affiliateMatchRate?: number | undefined;
|
|
292
|
+
affiliateMaxCap?: number | undefined;
|
|
293
|
+
dividendRate?: number | undefined;
|
|
294
|
+
marginConfig?: unknown;
|
|
295
|
+
};
|
|
296
|
+
output: void;
|
|
297
|
+
meta: object;
|
|
298
|
+
}>;
|
|
299
|
+
}>>;
|
|
300
|
+
}>>;
|