@open-mercato/tillio 0.7.1-develop.7176.1.d229567a50
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/.turbo/turbo-build.log +2 -0
- package/build.mjs +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-001-pull-readiness.spec.js +17 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-001-pull-readiness.spec.js.map +7 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-002-pull-blocked.spec.js +27 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-002-pull-blocked.spec.js.map +7 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-003-operators.spec.js +47 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-003-operators.spec.js.map +7 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-004-pull-widget.spec.js +11 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-004-pull-widget.spec.js.map +7 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-005-operators-widget.spec.js +13 -0
- package/dist/modules/tillio/__integration__/TC-PHONE-005-operators-widget.spec.js.map +7 -0
- package/dist/modules/tillio/acl.js +9 -0
- package/dist/modules/tillio/acl.js.map +7 -0
- package/dist/modules/tillio/api/operators/[id]/route.js +78 -0
- package/dist/modules/tillio/api/operators/[id]/route.js.map +7 -0
- package/dist/modules/tillio/api/operators/route.js +171 -0
- package/dist/modules/tillio/api/operators/route.js.map +7 -0
- package/dist/modules/tillio/api/pull/route.js +180 -0
- package/dist/modules/tillio/api/pull/route.js.map +7 -0
- package/dist/modules/tillio/cli.js +108 -0
- package/dist/modules/tillio/cli.js.map +7 -0
- package/dist/modules/tillio/di.js +16 -0
- package/dist/modules/tillio/di.js.map +7 -0
- package/dist/modules/tillio/index.js +12 -0
- package/dist/modules/tillio/index.js.map +7 -0
- package/dist/modules/tillio/integration.js +62 -0
- package/dist/modules/tillio/integration.js.map +7 -0
- package/dist/modules/tillio/lib/adapter.js +107 -0
- package/dist/modules/tillio/lib/adapter.js.map +7 -0
- package/dist/modules/tillio/lib/client.js +249 -0
- package/dist/modules/tillio/lib/client.js.map +7 -0
- package/dist/modules/tillio/lib/environment.js +34 -0
- package/dist/modules/tillio/lib/environment.js.map +7 -0
- package/dist/modules/tillio/lib/error-codes.js +59 -0
- package/dist/modules/tillio/lib/error-codes.js.map +7 -0
- package/dist/modules/tillio/lib/errors.js +12 -0
- package/dist/modules/tillio/lib/errors.js.map +7 -0
- package/dist/modules/tillio/lib/health.js +41 -0
- package/dist/modules/tillio/lib/health.js.map +7 -0
- package/dist/modules/tillio/lib/locking.js +18 -0
- package/dist/modules/tillio/lib/locking.js.map +7 -0
- package/dist/modules/tillio/lib/normalizer.js +134 -0
- package/dist/modules/tillio/lib/normalizer.js.map +7 -0
- package/dist/modules/tillio/lib/operators-store.js +62 -0
- package/dist/modules/tillio/lib/operators-store.js.map +7 -0
- package/dist/modules/tillio/lib/operators.js +168 -0
- package/dist/modules/tillio/lib/operators.js.map +7 -0
- package/dist/modules/tillio/lib/preset.js +122 -0
- package/dist/modules/tillio/lib/preset.js.map +7 -0
- package/dist/modules/tillio/lib/pull-job.js +265 -0
- package/dist/modules/tillio/lib/pull-job.js.map +7 -0
- package/dist/modules/tillio/lib/pull-readiness.js +36 -0
- package/dist/modules/tillio/lib/pull-readiness.js.map +7 -0
- package/dist/modules/tillio/lib/queue.js +22 -0
- package/dist/modules/tillio/lib/queue.js.map +7 -0
- package/dist/modules/tillio/lib/tz.js +85 -0
- package/dist/modules/tillio/lib/tz.js.map +7 -0
- package/dist/modules/tillio/lib/url-guard.js +24 -0
- package/dist/modules/tillio/lib/url-guard.js.map +7 -0
- package/dist/modules/tillio/setup.js +45 -0
- package/dist/modules/tillio/setup.js.map +7 -0
- package/dist/modules/tillio/widgets/injection/operators-config/widget.client.js +208 -0
- package/dist/modules/tillio/widgets/injection/operators-config/widget.client.js.map +7 -0
- package/dist/modules/tillio/widgets/injection/operators-config/widget.js +17 -0
- package/dist/modules/tillio/widgets/injection/operators-config/widget.js.map +7 -0
- package/dist/modules/tillio/widgets/injection/pull-calls/widget.client.js +148 -0
- package/dist/modules/tillio/widgets/injection/pull-calls/widget.client.js.map +7 -0
- package/dist/modules/tillio/widgets/injection/pull-calls/widget.js +17 -0
- package/dist/modules/tillio/widgets/injection/pull-calls/widget.js.map +7 -0
- package/dist/modules/tillio/widgets/injection-table.js +21 -0
- package/dist/modules/tillio/widgets/injection-table.js.map +7 -0
- package/dist/modules/tillio/workers/tillio-pull.js +17 -0
- package/dist/modules/tillio/workers/tillio-pull.js.map +7 -0
- package/jest.config.cjs +35 -0
- package/package.json +92 -0
- package/src/index.ts +1 -0
- package/src/modules/tillio/__integration__/TC-PHONE-001-pull-readiness.spec.ts +38 -0
- package/src/modules/tillio/__integration__/TC-PHONE-002-pull-blocked.spec.ts +44 -0
- package/src/modules/tillio/__integration__/TC-PHONE-003-operators.spec.ts +76 -0
- package/src/modules/tillio/__integration__/TC-PHONE-004-pull-widget.spec.ts +22 -0
- package/src/modules/tillio/__integration__/TC-PHONE-005-operators-widget.spec.ts +27 -0
- package/src/modules/tillio/__tests__/adapter-fetch-calls.test.ts +148 -0
- package/src/modules/tillio/__tests__/client.test.ts +203 -0
- package/src/modules/tillio/__tests__/health.test.ts +122 -0
- package/src/modules/tillio/__tests__/normalizer.test.ts +143 -0
- package/src/modules/tillio/__tests__/operators-routes.test.ts +100 -0
- package/src/modules/tillio/__tests__/operators-store.test.ts +104 -0
- package/src/modules/tillio/__tests__/operators.test.ts +250 -0
- package/src/modules/tillio/__tests__/preset.test.ts +337 -0
- package/src/modules/tillio/__tests__/pull-body.test.ts +43 -0
- package/src/modules/tillio/__tests__/pull-job.test.ts +250 -0
- package/src/modules/tillio/__tests__/pull-readiness.test.ts +143 -0
- package/src/modules/tillio/__tests__/pull-route.test.ts +148 -0
- package/src/modules/tillio/__tests__/tz.test.ts +53 -0
- package/src/modules/tillio/__tests__/url-guard.test.ts +46 -0
- package/src/modules/tillio/acl.ts +5 -0
- package/src/modules/tillio/api/operators/[id]/route.ts +90 -0
- package/src/modules/tillio/api/operators/route.ts +191 -0
- package/src/modules/tillio/api/pull/route.ts +213 -0
- package/src/modules/tillio/cli.ts +125 -0
- package/src/modules/tillio/di.ts +16 -0
- package/src/modules/tillio/i18n/de.json +73 -0
- package/src/modules/tillio/i18n/en.json +73 -0
- package/src/modules/tillio/i18n/es.json +73 -0
- package/src/modules/tillio/i18n/ko.json +73 -0
- package/src/modules/tillio/i18n/pl.json +73 -0
- package/src/modules/tillio/index.ts +8 -0
- package/src/modules/tillio/integration.ts +58 -0
- package/src/modules/tillio/lib/adapter.ts +135 -0
- package/src/modules/tillio/lib/client.ts +329 -0
- package/src/modules/tillio/lib/environment.ts +53 -0
- package/src/modules/tillio/lib/error-codes.ts +67 -0
- package/src/modules/tillio/lib/errors.ts +11 -0
- package/src/modules/tillio/lib/health.ts +57 -0
- package/src/modules/tillio/lib/locking.ts +30 -0
- package/src/modules/tillio/lib/normalizer.ts +184 -0
- package/src/modules/tillio/lib/operators-store.ts +77 -0
- package/src/modules/tillio/lib/operators.ts +269 -0
- package/src/modules/tillio/lib/preset.ts +226 -0
- package/src/modules/tillio/lib/pull-job.ts +390 -0
- package/src/modules/tillio/lib/pull-readiness.ts +77 -0
- package/src/modules/tillio/lib/queue.ts +33 -0
- package/src/modules/tillio/lib/tz.ts +121 -0
- package/src/modules/tillio/lib/url-guard.ts +34 -0
- package/src/modules/tillio/setup.ts +49 -0
- package/src/modules/tillio/widgets/injection/operators-config/widget.client.tsx +304 -0
- package/src/modules/tillio/widgets/injection/operators-config/widget.ts +16 -0
- package/src/modules/tillio/widgets/injection/pull-calls/widget.client.tsx +214 -0
- package/src/modules/tillio/widgets/injection/pull-calls/widget.ts +16 -0
- package/src/modules/tillio/widgets/injection-table.ts +19 -0
- package/src/modules/tillio/workers/tillio-pull.ts +18 -0
- package/tsconfig.json +9 -0
package/build.mjs
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { apiRequest, getAuthToken } from "@open-mercato/core/helpers/integration/api";
|
|
3
|
+
import { readJsonSafe } from "@open-mercato/core/helpers/integration/generalFixtures";
|
|
4
|
+
test.describe("TC-PHONE-001: Tillio pull readiness", () => {
|
|
5
|
+
test("reports environment_not_ready when Tillio is not configured", async ({ request }) => {
|
|
6
|
+
const token = await getAuthToken(request, "superadmin");
|
|
7
|
+
const response = await apiRequest(request, "GET", "/api/tillio/pull", { token });
|
|
8
|
+
expect(response.status(), "the readiness probe should be reachable").toBe(200);
|
|
9
|
+
const body = await readJsonSafe(response);
|
|
10
|
+
expect(body?.ok, "readiness responds ok even when blocked").toBe(true);
|
|
11
|
+
expect(body?.environmentReady, "no Tillio credentials means the environment is not ready").toBe(false);
|
|
12
|
+
expect(body?.operatorAttached, "no operator can be attached without an environment").toBe(false);
|
|
13
|
+
expect(body?.blocker, "the environment blocker should be reported").toBe("environment_not_ready");
|
|
14
|
+
expect(body?.operatorId, "no operator id without an operator").toBeNull();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=TC-PHONE-001-pull-readiness.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/tillio/__integration__/TC-PHONE-001-pull-readiness.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { apiRequest, getAuthToken } from '@open-mercato/core/helpers/integration/api'\nimport { readJsonSafe } from '@open-mercato/core/helpers/integration/generalFixtures'\n\ntype ReadinessBody = {\n ok?: boolean\n environmentReady?: boolean\n operatorAttached?: boolean\n envDrift?: boolean\n blocker?: string | null\n operatorId?: string | null\n plugin?: string | null\n}\n\n/**\n * TC-PHONE-001 \u2014 the pull readiness probe reports an unconfigured Tillio environment.\n *\n * GET /api/tillio/pull only reads config out of the database; it never calls Tillio.\n * On a fresh environment no Tillio credentials exist, so readiness must report the\n * `environment_not_ready` blocker rather than erroring. This asserts the module's own\n * readiness contract \u2014 the blocker precedence rules themselves are unit-tested in\n * packages/tillio/src/modules/tillio/__tests__/pull-readiness.test.ts.\n */\ntest.describe('TC-PHONE-001: Tillio pull readiness', () => {\n test('reports environment_not_ready when Tillio is not configured', async ({ request }) => {\n const token = await getAuthToken(request, 'superadmin')\n const response = await apiRequest(request, 'GET', '/api/tillio/pull', { token })\n\n expect(response.status(), 'the readiness probe should be reachable').toBe(200)\n\n const body = await readJsonSafe<ReadinessBody>(response)\n expect(body?.ok, 'readiness responds ok even when blocked').toBe(true)\n expect(body?.environmentReady, 'no Tillio credentials means the environment is not ready').toBe(false)\n expect(body?.operatorAttached, 'no operator can be attached without an environment').toBe(false)\n expect(body?.blocker, 'the environment blocker should be reported').toBe('environment_not_ready')\n expect(body?.operatorId, 'no operator id without an operator').toBeNull()\n })\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,YAAY,oBAAoB;AACzC,SAAS,oBAAoB;AAqB7B,KAAK,SAAS,uCAAuC,MAAM;AACzD,OAAK,+DAA+D,OAAO,EAAE,QAAQ,MAAM;AACzF,UAAM,QAAQ,MAAM,aAAa,SAAS,YAAY;AACtD,UAAM,WAAW,MAAM,WAAW,SAAS,OAAO,oBAAoB,EAAE,MAAM,CAAC;AAE/E,WAAO,SAAS,OAAO,GAAG,yCAAyC,EAAE,KAAK,GAAG;AAE7E,UAAM,OAAO,MAAM,aAA4B,QAAQ;AACvD,WAAO,MAAM,IAAI,yCAAyC,EAAE,KAAK,IAAI;AACrE,WAAO,MAAM,kBAAkB,0DAA0D,EAAE,KAAK,KAAK;AACrG,WAAO,MAAM,kBAAkB,oDAAoD,EAAE,KAAK,KAAK;AAC/F,WAAO,MAAM,SAAS,4CAA4C,EAAE,KAAK,uBAAuB;AAChG,WAAO,MAAM,YAAY,oCAAoC,EAAE,SAAS;AAAA,EAC1E,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { apiRequest, getAuthToken } from "@open-mercato/core/helpers/integration/api";
|
|
3
|
+
import { readJsonSafe } from "@open-mercato/core/helpers/integration/generalFixtures";
|
|
4
|
+
test.describe("TC-PHONE-002: Tillio pull blocked before reaching the provider", () => {
|
|
5
|
+
test("returns a structured 409 when the environment is not configured", async ({ request }) => {
|
|
6
|
+
const token = await getAuthToken(request, "superadmin");
|
|
7
|
+
const response = await apiRequest(request, "POST", "/api/tillio/pull", {
|
|
8
|
+
token,
|
|
9
|
+
data: { from: "2026-03-01", to: "2026-03-02" }
|
|
10
|
+
});
|
|
11
|
+
expect(response.status(), "an unconfigured environment must block the pull").toBe(409);
|
|
12
|
+
const body = await readJsonSafe(response);
|
|
13
|
+
expect(body?.ok).toBe(false);
|
|
14
|
+
expect(body?.code, "the blocker code drives which settings section the UI opens").toBe("environment_not_ready");
|
|
15
|
+
expect(body?.section, "environment blockers route to the environment section").toBe("environment");
|
|
16
|
+
expect(body?.message, "the blocker carries an actionable message").toBeTruthy();
|
|
17
|
+
});
|
|
18
|
+
test("rejects a reversed day range before touching readiness", async ({ request }) => {
|
|
19
|
+
const token = await getAuthToken(request, "superadmin");
|
|
20
|
+
const response = await apiRequest(request, "POST", "/api/tillio/pull", {
|
|
21
|
+
token,
|
|
22
|
+
data: { from: "2026-03-02", to: "2026-03-01" }
|
|
23
|
+
});
|
|
24
|
+
expect(response.status(), "from must not be after to").toBe(400);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=TC-PHONE-002-pull-blocked.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/tillio/__integration__/TC-PHONE-002-pull-blocked.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { apiRequest, getAuthToken } from '@open-mercato/core/helpers/integration/api'\nimport { readJsonSafe } from '@open-mercato/core/helpers/integration/generalFixtures'\n\ntype BlockedBody = { ok?: boolean; code?: string; section?: string; message?: string }\n\n/**\n * TC-PHONE-002 \u2014 the pull refuses to run before Tillio is configured.\n *\n * The readiness gate returns 409 before `tillioAdapter.fetchCalls` is ever reached, so\n * this exercises the real POST path without any network traffic to Tillio and without\n * ingesting anything. That ordering is the point of the test: a regression that moved\n * the gate below the fetch would turn an unconfigured tenant into a live provider call.\n *\n * A pull against a real Tillio environment needs Ringostat credentials and is therefore\n * a deliberate manual check, not an automated one.\n */\ntest.describe('TC-PHONE-002: Tillio pull blocked before reaching the provider', () => {\n test('returns a structured 409 when the environment is not configured', async ({ request }) => {\n const token = await getAuthToken(request, 'superadmin')\n const response = await apiRequest(request, 'POST', '/api/tillio/pull', {\n token,\n data: { from: '2026-03-01', to: '2026-03-02' },\n })\n\n expect(response.status(), 'an unconfigured environment must block the pull').toBe(409)\n\n const body = await readJsonSafe<BlockedBody>(response)\n expect(body?.ok).toBe(false)\n expect(body?.code, 'the blocker code drives which settings section the UI opens').toBe('environment_not_ready')\n expect(body?.section, 'environment blockers route to the environment section').toBe('environment')\n expect(body?.message, 'the blocker carries an actionable message').toBeTruthy()\n })\n\n test('rejects a reversed day range before touching readiness', async ({ request }) => {\n const token = await getAuthToken(request, 'superadmin')\n const response = await apiRequest(request, 'POST', '/api/tillio/pull', {\n token,\n data: { from: '2026-03-02', to: '2026-03-01' },\n })\n\n expect(response.status(), 'from must not be after to').toBe(400)\n })\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,YAAY,oBAAoB;AACzC,SAAS,oBAAoB;AAe7B,KAAK,SAAS,kEAAkE,MAAM;AACpF,OAAK,mEAAmE,OAAO,EAAE,QAAQ,MAAM;AAC7F,UAAM,QAAQ,MAAM,aAAa,SAAS,YAAY;AACtD,UAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,oBAAoB;AAAA,MACrE;AAAA,MACA,MAAM,EAAE,MAAM,cAAc,IAAI,aAAa;AAAA,IAC/C,CAAC;AAED,WAAO,SAAS,OAAO,GAAG,iDAAiD,EAAE,KAAK,GAAG;AAErF,UAAM,OAAO,MAAM,aAA0B,QAAQ;AACrD,WAAO,MAAM,EAAE,EAAE,KAAK,KAAK;AAC3B,WAAO,MAAM,MAAM,6DAA6D,EAAE,KAAK,uBAAuB;AAC9G,WAAO,MAAM,SAAS,uDAAuD,EAAE,KAAK,aAAa;AACjG,WAAO,MAAM,SAAS,2CAA2C,EAAE,WAAW;AAAA,EAChF,CAAC;AAED,OAAK,0DAA0D,OAAO,EAAE,QAAQ,MAAM;AACpF,UAAM,QAAQ,MAAM,aAAa,SAAS,YAAY;AACtD,UAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,oBAAoB;AAAA,MACrE;AAAA,MACA,MAAM,EAAE,MAAM,cAAc,IAAI,aAAa;AAAA,IAC/C,CAAC;AAED,WAAO,SAAS,OAAO,GAAG,2BAA2B,EAAE,KAAK,GAAG;AAAA,EACjE,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { apiRequest, getAuthToken } from "@open-mercato/core/helpers/integration/api";
|
|
3
|
+
import { readJsonSafe } from "@open-mercato/core/helpers/integration/generalFixtures";
|
|
4
|
+
test.describe("TC-PHONE-003: Tillio operators", () => {
|
|
5
|
+
test("lists an empty operator set on an unconfigured environment", async ({ request }) => {
|
|
6
|
+
const token = await getAuthToken(request, "superadmin");
|
|
7
|
+
const response = await apiRequest(request, "GET", "/api/tillio/operators", { token });
|
|
8
|
+
expect(response.status(), "the operator listing should be reachable").toBe(200);
|
|
9
|
+
const body = await readJsonSafe(response);
|
|
10
|
+
expect(body?.ok).toBe(true);
|
|
11
|
+
expect(body?.environmentReady, "no credentials means the environment is not ready").toBe(false);
|
|
12
|
+
expect(body?.operators, "no operators can exist without an environment").toEqual([]);
|
|
13
|
+
expect(body?.defaultOperatorId).toBeNull();
|
|
14
|
+
expect(body?.tenantSystemId).toBeNull();
|
|
15
|
+
expect(body?.envDrift, "drift is meaningless with no operators attached").toBe(false);
|
|
16
|
+
expect(body?.supportedPlugins, "the UI builds its plugin picker from this list").toEqual(["Ringostat"]);
|
|
17
|
+
});
|
|
18
|
+
test("refuses to attach an operator before the environment is healthy", async ({ request }) => {
|
|
19
|
+
const token = await getAuthToken(request, "superadmin");
|
|
20
|
+
const response = await apiRequest(request, "POST", "/api/tillio/operators", {
|
|
21
|
+
token,
|
|
22
|
+
data: { plugin: "Ringostat", config: { key: "qa-tillio-003-never-used" } }
|
|
23
|
+
});
|
|
24
|
+
expect(response.status(), "attaching must be gated on a healthy environment").toBe(409);
|
|
25
|
+
const body = await readJsonSafe(response);
|
|
26
|
+
expect(body?.ok).toBe(false);
|
|
27
|
+
expect(body?.code).toBe("environment_not_ready");
|
|
28
|
+
expect(body?.section, "the blocker routes the UI to the environment section").toBe("environment");
|
|
29
|
+
});
|
|
30
|
+
test("rejects an unsupported plugin", async ({ request }) => {
|
|
31
|
+
const token = await getAuthToken(request, "superadmin");
|
|
32
|
+
const response = await apiRequest(request, "POST", "/api/tillio/operators", {
|
|
33
|
+
token,
|
|
34
|
+
data: { plugin: "Twilio", config: { key: "qa-tillio-003-never-used" } }
|
|
35
|
+
});
|
|
36
|
+
expect(response.status(), "only Ringostat is a supported plugin today").toBe(400);
|
|
37
|
+
});
|
|
38
|
+
test("detaching an unknown operator reports that nothing was detached", async ({ request }) => {
|
|
39
|
+
const token = await getAuthToken(request, "superadmin");
|
|
40
|
+
const response = await apiRequest(request, "DELETE", "/api/tillio/operators/qa-tillio-003-missing", { token });
|
|
41
|
+
expect(response.status()).toBe(200);
|
|
42
|
+
const body = await readJsonSafe(response);
|
|
43
|
+
expect(body?.ok).toBe(true);
|
|
44
|
+
expect(body?.detached, "an unknown id detaches nothing rather than erroring").toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=TC-PHONE-003-operators.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/tillio/__integration__/TC-PHONE-003-operators.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { apiRequest, getAuthToken } from '@open-mercato/core/helpers/integration/api'\nimport { readJsonSafe } from '@open-mercato/core/helpers/integration/generalFixtures'\n\ntype OperatorsBody = {\n ok?: boolean\n environmentReady?: boolean\n tenantSystemId?: string | null\n supportedPlugins?: string[]\n operators?: Array<{ id?: string; plugin?: string; stale?: boolean }>\n defaultOperatorId?: string | null\n envDrift?: boolean\n}\ntype BlockedBody = { ok?: boolean; code?: string; section?: string; message?: string }\n\n/**\n * TC-PHONE-003 \u2014 the operator routes answer correctly before Tillio is configured.\n *\n * Attaching a real operator calls Tillio (`addConfig`) and needs a Ringostat key, so the\n * live path stays a manual check. Everything asserted here stops before the network:\n * the listing reads credentials out of the database, the attach is refused by the\n * environment gate ahead of `attachOperator`, and detaching an unknown id returns early\n * without resolving an environment at all.\n */\ntest.describe('TC-PHONE-003: Tillio operators', () => {\n test('lists an empty operator set on an unconfigured environment', async ({ request }) => {\n const token = await getAuthToken(request, 'superadmin')\n const response = await apiRequest(request, 'GET', '/api/tillio/operators', { token })\n\n expect(response.status(), 'the operator listing should be reachable').toBe(200)\n\n const body = await readJsonSafe<OperatorsBody>(response)\n expect(body?.ok).toBe(true)\n expect(body?.environmentReady, 'no credentials means the environment is not ready').toBe(false)\n expect(body?.operators, 'no operators can exist without an environment').toEqual([])\n expect(body?.defaultOperatorId).toBeNull()\n expect(body?.tenantSystemId).toBeNull()\n expect(body?.envDrift, 'drift is meaningless with no operators attached').toBe(false)\n expect(body?.supportedPlugins, 'the UI builds its plugin picker from this list').toEqual(['Ringostat'])\n })\n\n test('refuses to attach an operator before the environment is healthy', async ({ request }) => {\n const token = await getAuthToken(request, 'superadmin')\n const response = await apiRequest(request, 'POST', '/api/tillio/operators', {\n token,\n data: { plugin: 'Ringostat', config: { key: 'qa-tillio-003-never-used' } },\n })\n\n expect(response.status(), 'attaching must be gated on a healthy environment').toBe(409)\n\n const body = await readJsonSafe<BlockedBody>(response)\n expect(body?.ok).toBe(false)\n expect(body?.code).toBe('environment_not_ready')\n expect(body?.section, 'the blocker routes the UI to the environment section').toBe('environment')\n })\n\n test('rejects an unsupported plugin', async ({ request }) => {\n const token = await getAuthToken(request, 'superadmin')\n const response = await apiRequest(request, 'POST', '/api/tillio/operators', {\n token,\n data: { plugin: 'Twilio', config: { key: 'qa-tillio-003-never-used' } },\n })\n\n expect(response.status(), 'only Ringostat is a supported plugin today').toBe(400)\n })\n\n test('detaching an unknown operator reports that nothing was detached', async ({ request }) => {\n const token = await getAuthToken(request, 'superadmin')\n const response = await apiRequest(request, 'DELETE', '/api/tillio/operators/qa-tillio-003-missing', { token })\n\n expect(response.status()).toBe(200)\n const body = await readJsonSafe<{ ok?: boolean; detached?: boolean }>(response)\n expect(body?.ok).toBe(true)\n expect(body?.detached, 'an unknown id detaches nothing rather than erroring').toBe(false)\n })\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,YAAY,oBAAoB;AACzC,SAAS,oBAAoB;AAsB7B,KAAK,SAAS,kCAAkC,MAAM;AACpD,OAAK,8DAA8D,OAAO,EAAE,QAAQ,MAAM;AACxF,UAAM,QAAQ,MAAM,aAAa,SAAS,YAAY;AACtD,UAAM,WAAW,MAAM,WAAW,SAAS,OAAO,yBAAyB,EAAE,MAAM,CAAC;AAEpF,WAAO,SAAS,OAAO,GAAG,0CAA0C,EAAE,KAAK,GAAG;AAE9E,UAAM,OAAO,MAAM,aAA4B,QAAQ;AACvD,WAAO,MAAM,EAAE,EAAE,KAAK,IAAI;AAC1B,WAAO,MAAM,kBAAkB,mDAAmD,EAAE,KAAK,KAAK;AAC9F,WAAO,MAAM,WAAW,+CAA+C,EAAE,QAAQ,CAAC,CAAC;AACnF,WAAO,MAAM,iBAAiB,EAAE,SAAS;AACzC,WAAO,MAAM,cAAc,EAAE,SAAS;AACtC,WAAO,MAAM,UAAU,iDAAiD,EAAE,KAAK,KAAK;AACpF,WAAO,MAAM,kBAAkB,gDAAgD,EAAE,QAAQ,CAAC,WAAW,CAAC;AAAA,EACxG,CAAC;AAED,OAAK,mEAAmE,OAAO,EAAE,QAAQ,MAAM;AAC7F,UAAM,QAAQ,MAAM,aAAa,SAAS,YAAY;AACtD,UAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,yBAAyB;AAAA,MAC1E;AAAA,MACA,MAAM,EAAE,QAAQ,aAAa,QAAQ,EAAE,KAAK,2BAA2B,EAAE;AAAA,IAC3E,CAAC;AAED,WAAO,SAAS,OAAO,GAAG,kDAAkD,EAAE,KAAK,GAAG;AAEtF,UAAM,OAAO,MAAM,aAA0B,QAAQ;AACrD,WAAO,MAAM,EAAE,EAAE,KAAK,KAAK;AAC3B,WAAO,MAAM,IAAI,EAAE,KAAK,uBAAuB;AAC/C,WAAO,MAAM,SAAS,sDAAsD,EAAE,KAAK,aAAa;AAAA,EAClG,CAAC;AAED,OAAK,iCAAiC,OAAO,EAAE,QAAQ,MAAM;AAC3D,UAAM,QAAQ,MAAM,aAAa,SAAS,YAAY;AACtD,UAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,yBAAyB;AAAA,MAC1E;AAAA,MACA,MAAM,EAAE,QAAQ,UAAU,QAAQ,EAAE,KAAK,2BAA2B,EAAE;AAAA,IACxE,CAAC;AAED,WAAO,SAAS,OAAO,GAAG,4CAA4C,EAAE,KAAK,GAAG;AAAA,EAClF,CAAC;AAED,OAAK,mEAAmE,OAAO,EAAE,QAAQ,MAAM;AAC7F,UAAM,QAAQ,MAAM,aAAa,SAAS,YAAY;AACtD,UAAM,WAAW,MAAM,WAAW,SAAS,UAAU,+CAA+C,EAAE,MAAM,CAAC;AAE7G,WAAO,SAAS,OAAO,CAAC,EAAE,KAAK,GAAG;AAClC,UAAM,OAAO,MAAM,aAAmD,QAAQ;AAC9E,WAAO,MAAM,EAAE,EAAE,KAAK,IAAI;AAC1B,WAAO,MAAM,UAAU,qDAAqD,EAAE,KAAK,KAAK;AAAA,EAC1F,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { login } from "@open-mercato/core/helpers/integration/auth";
|
|
3
|
+
test.describe("TC-PHONE-004: Tillio pull widget injection", () => {
|
|
4
|
+
test("renders the pull action on the phone calls list", async ({ page }) => {
|
|
5
|
+
await login(page, "admin");
|
|
6
|
+
await page.goto("/backend/phone_calls");
|
|
7
|
+
await expect(page.getByRole("heading", { name: "Phone Calls" })).toBeVisible();
|
|
8
|
+
await expect(page.getByRole("button", { name: "Pull calls" })).toBeVisible();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=TC-PHONE-004-pull-widget.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/tillio/__integration__/TC-PHONE-004-pull-widget.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { login } from '@open-mercato/core/helpers/integration/auth'\n\n/**\n * TC-PHONE-004 \u2014 the Pull widget reaches the hub's call list toolbar.\n *\n * This module injects `tillio.injection.pull-calls` into `data-table:phone_calls.calls:toolbar`,\n * a spot owned by phone_calls in another package. Nothing in either package fails to compile if\n * that wiring breaks \u2014 the button simply stops rendering \u2014 so the injection is asserted here.\n *\n * It lives in the provider package on purpose: phone_calls must keep passing with tillio\n * disabled, so its own page spec cannot assert a Tillio button.\n */\ntest.describe('TC-PHONE-004: Tillio pull widget injection', () => {\n test('renders the pull action on the phone calls list', async ({ page }) => {\n await login(page, 'admin')\n await page.goto('/backend/phone_calls')\n\n await expect(page.getByRole('heading', { name: 'Phone Calls' })).toBeVisible()\n await expect(page.getByRole('button', { name: 'Pull calls' })).toBeVisible()\n })\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,aAAa;AAYtB,KAAK,SAAS,8CAA8C,MAAM;AAChE,OAAK,mDAAmD,OAAO,EAAE,KAAK,MAAM;AAC1E,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,KAAK,KAAK,sBAAsB;AAEtC,UAAM,OAAO,KAAK,UAAU,WAAW,EAAE,MAAM,cAAc,CAAC,CAAC,EAAE,YAAY;AAC7E,UAAM,OAAO,KAAK,UAAU,UAAU,EAAE,MAAM,aAAa,CAAC,CAAC,EAAE,YAAY;AAAA,EAC7E,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { login } from "@open-mercato/core/helpers/integration/auth";
|
|
3
|
+
test.describe("TC-PHONE-005: Tillio operators widget injection", () => {
|
|
4
|
+
test("renders the operator configuration tab with not-ready guidance", async ({ page }) => {
|
|
5
|
+
await login(page, "admin");
|
|
6
|
+
await page.goto("/backend/integrations/tillio");
|
|
7
|
+
await page.getByRole("tab", { name: "Operator configuration" }).click();
|
|
8
|
+
await expect(
|
|
9
|
+
page.getByText("Save the Tillio API URL and key in the Credentials tab, then run the health Check before attaching an operator.")
|
|
10
|
+
).toBeVisible();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=TC-PHONE-005-operators-widget.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/tillio/__integration__/TC-PHONE-005-operators-widget.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { login } from '@open-mercato/core/helpers/integration/auth'\n\n/**\n * TC-PHONE-005 \u2014 the operator configuration tab reaches the Tillio integration page.\n *\n * `tillio.injection.operators` is injected as a tab into this integration's detail spot,\n * which the integrations module renders from the `IntegrationDefinition`. The registration\n * chain (definition -> spot id -> injection table) has no compile-time link to the page, so\n * a break would silently drop the tab.\n *\n * The page opens on Credentials, so the tab is activated before asserting its body. With no\n * credentials stored the widget must guide the user to configure them instead of offering an\n * operator form \u2014 that guidance is the state a fresh tenant actually sees.\n */\ntest.describe('TC-PHONE-005: Tillio operators widget injection', () => {\n test('renders the operator configuration tab with not-ready guidance', async ({ page }) => {\n await login(page, 'admin')\n await page.goto('/backend/integrations/tillio')\n\n await page.getByRole('tab', { name: 'Operator configuration' }).click()\n\n await expect(\n page.getByText('Save the Tillio API URL and key in the Credentials tab, then run the health Check before attaching an operator.'),\n ).toBeVisible()\n })\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,aAAa;AActB,KAAK,SAAS,mDAAmD,MAAM;AACrE,OAAK,kEAAkE,OAAO,EAAE,KAAK,MAAM;AACzF,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,KAAK,KAAK,8BAA8B;AAE9C,UAAM,KAAK,UAAU,OAAO,EAAE,MAAM,yBAAyB,CAAC,EAAE,MAAM;AAEtE,UAAM;AAAA,MACJ,KAAK,UAAU,iHAAiH;AAAA,IAClI,EAAE,YAAY;AAAA,EAChB,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/tillio/acl.ts"],
|
|
4
|
+
"sourcesContent": ["export const features = [\n { id: 'tillio.manage', title: 'Manage Tillio integration', module: 'tillio' },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,iBAAiB,OAAO,6BAA6B,QAAQ,SAAS;AAC9E;AAEA,IAAO,cAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
6
|
+
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
7
|
+
import { tillioErrorCopy } from "../../../lib/error-codes.js";
|
|
8
|
+
import {
|
|
9
|
+
detachOperator,
|
|
10
|
+
TILLIO_OPERATOR_RESOURCE_KIND,
|
|
11
|
+
TillioRevocationFailedError
|
|
12
|
+
} from "../../../lib/operators.js";
|
|
13
|
+
const idParamsSchema = z.object({ id: z.string().trim().min(1) });
|
|
14
|
+
const metadata = {
|
|
15
|
+
DELETE: { requireAuth: true, requireFeatures: ["tillio.manage", "integrations.manage"] }
|
|
16
|
+
};
|
|
17
|
+
const openApi = {
|
|
18
|
+
tags: ["Tillio"],
|
|
19
|
+
summary: "Detach a Tillio operator"
|
|
20
|
+
};
|
|
21
|
+
async function DELETE(req, ctx) {
|
|
22
|
+
const auth = await getAuthFromRequest(req);
|
|
23
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
24
|
+
return NextResponse.json({ ok: false, code: "unauthorized", message: "Unauthorized" }, { status: 401 });
|
|
25
|
+
}
|
|
26
|
+
const rawParams = ctx.params && typeof ctx.params.then === "function" ? await ctx.params : ctx.params;
|
|
27
|
+
const parsedParams = idParamsSchema.safeParse(rawParams);
|
|
28
|
+
if (!parsedParams.success) {
|
|
29
|
+
return NextResponse.json({ ok: false, code: "invalid_operator_id", message: "Invalid operator id" }, { status: 400 });
|
|
30
|
+
}
|
|
31
|
+
const operatorId = parsedParams.data.id;
|
|
32
|
+
const force = parseBooleanWithDefault(new URL(req.url).searchParams.get("force"), false);
|
|
33
|
+
const container = await createRequestContainer();
|
|
34
|
+
const credentialsService = container.resolve("integrationCredentialsService");
|
|
35
|
+
const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
|
|
36
|
+
const guarded = await runRouteMutationGuards({
|
|
37
|
+
container,
|
|
38
|
+
req,
|
|
39
|
+
auth: { userId: auth.sub, tenantId: auth.tenantId, organizationId: auth.orgId },
|
|
40
|
+
input: {
|
|
41
|
+
resourceKind: TILLIO_OPERATOR_RESOURCE_KIND,
|
|
42
|
+
resourceId: operatorId,
|
|
43
|
+
operation: "delete",
|
|
44
|
+
mutationPayload: { operatorId, force }
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
if (!guarded.ok) return guarded.response;
|
|
48
|
+
const guardedForce = typeof guarded.modifiedPayload?.force === "boolean" ? guarded.modifiedPayload.force : force;
|
|
49
|
+
try {
|
|
50
|
+
const result = await detachOperator({ credentialsService, scope }, operatorId, { force: guardedForce });
|
|
51
|
+
await guarded.runAfterSuccess();
|
|
52
|
+
return NextResponse.json({ ok: true, detached: result.detached, revoked: result.revoked });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err instanceof TillioRevocationFailedError) {
|
|
55
|
+
const code = err.environmentMissing ? "environment_not_ready" : "revocation_failed";
|
|
56
|
+
return NextResponse.json(
|
|
57
|
+
{
|
|
58
|
+
ok: false,
|
|
59
|
+
code,
|
|
60
|
+
section: err.environmentMissing ? "environment" : "operator",
|
|
61
|
+
message: tillioErrorCopy(code, "detach_failed").fallback,
|
|
62
|
+
canForce: true
|
|
63
|
+
},
|
|
64
|
+
{ status: 502 }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return NextResponse.json(
|
|
68
|
+
{ ok: false, code: "detach_failed", section: "operator", message: "Failed to detach the operator." },
|
|
69
|
+
{ status: 500 }
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
DELETE,
|
|
75
|
+
metadata,
|
|
76
|
+
openApi
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/tillio/api/operators/%5Bid%5D/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'\nimport { tillioErrorCopy } from '../../../lib/error-codes'\nimport type { TillioCredentialsService } from '../../../lib/operators-store'\nimport {\n detachOperator,\n TILLIO_OPERATOR_RESOURCE_KIND,\n TillioRevocationFailedError,\n} from '../../../lib/operators'\n\nconst idParamsSchema = z.object({ id: z.string().trim().min(1) })\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['tillio.manage', 'integrations.manage'] },\n}\n\nexport const openApi = {\n tags: ['Tillio'],\n summary: 'Detach a Tillio operator',\n}\n\nexport async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ ok: false, code: 'unauthorized', message: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ ok: false, code: 'invalid_operator_id', message: 'Invalid operator id' }, { status: 400 })\n }\n const operatorId = parsedParams.data.id\n const force = parseBooleanWithDefault(new URL(req.url).searchParams.get('force'), false)\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as TillioCredentialsService\n const scope: IntegrationScope = { organizationId: auth.orgId, tenantId: auth.tenantId }\n\n const guarded = await runRouteMutationGuards({\n container,\n req,\n auth: { userId: auth.sub, tenantId: auth.tenantId, organizationId: auth.orgId },\n input: {\n resourceKind: TILLIO_OPERATOR_RESOURCE_KIND,\n resourceId: operatorId,\n operation: 'delete',\n mutationPayload: { operatorId, force },\n },\n })\n if (!guarded.ok) return guarded.response\n\n // A guard that refuses to let `force` through has to be able to say so. Only the flag is\n // read back: rewriting which operator gets detached would silently retarget the request.\n const guardedForce = typeof guarded.modifiedPayload?.force === 'boolean'\n ? guarded.modifiedPayload.force\n : force\n\n try {\n const result = await detachOperator({ credentialsService, scope }, operatorId, { force: guardedForce })\n await guarded.runAfterSuccess()\n return NextResponse.json({ ok: true, detached: result.detached, revoked: result.revoked })\n } catch (err) {\n if (err instanceof TillioRevocationFailedError) {\n const code = err.environmentMissing ? 'environment_not_ready' : 'revocation_failed'\n return NextResponse.json(\n {\n ok: false,\n code,\n section: err.environmentMissing ? 'environment' : 'operator',\n message: tillioErrorCopy(code, 'detach_failed').fallback,\n canForce: true,\n },\n { status: 502 },\n )\n }\n return NextResponse.json(\n { ok: false, code: 'detach_failed', section: 'operator', message: 'Failed to detach the operator.' },\n { status: 500 },\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AAExC,SAAS,uBAAuB;AAEhC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;AAEzD,MAAM,WAAW;AAAA,EACtB,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,qBAAqB,EAAE;AACzF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,QAAQ;AAAA,EACf,SAAS;AACX;AAEA,eAAsB,OAAO,KAAc,KAA8D;AACvG,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,MAAM,gBAAgB,SAAS,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxG;AAEA,QAAM,YAAa,IAAI,UAAU,OAAQ,IAAI,OAA4B,SAAS,aAC9E,MAAO,IAAI,SACV,IAAI;AAET,QAAM,eAAe,eAAe,UAAU,SAAS;AACvD,MAAI,CAAC,aAAa,SAAS;AACzB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,MAAM,uBAAuB,SAAS,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtH;AACA,QAAM,aAAa,aAAa,KAAK;AACrC,QAAM,QAAQ,wBAAwB,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa,IAAI,OAAO,GAAG,KAAK;AAEvF,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,QAA0B,EAAE,gBAAgB,KAAK,OAAO,UAAU,KAAK,SAAS;AAEtF,QAAM,UAAU,MAAM,uBAAuB;AAAA,IAC3C;AAAA,IACA;AAAA,IACA,MAAM,EAAE,QAAQ,KAAK,KAAK,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM;AAAA,IAC9E,OAAO;AAAA,MACL,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,iBAAiB,EAAE,YAAY,MAAM;AAAA,IACvC;AAAA,EACF,CAAC;AACD,MAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAIhC,QAAM,eAAe,OAAO,QAAQ,iBAAiB,UAAU,YAC3D,QAAQ,gBAAgB,QACxB;AAEJ,MAAI;AACF,UAAM,SAAS,MAAM,eAAe,EAAE,oBAAoB,MAAM,GAAG,YAAY,EAAE,OAAO,aAAa,CAAC;AACtG,UAAM,QAAQ,gBAAgB;AAC9B,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,UAAU,OAAO,UAAU,SAAS,OAAO,QAAQ,CAAC;AAAA,EAC3F,SAAS,KAAK;AACZ,QAAI,eAAe,6BAA6B;AAC9C,YAAM,OAAO,IAAI,qBAAqB,0BAA0B;AAChE,aAAO,aAAa;AAAA,QAClB;AAAA,UACE,IAAI;AAAA,UACJ;AAAA,UACA,SAAS,IAAI,qBAAqB,gBAAgB;AAAA,UAClD,SAAS,gBAAgB,MAAM,eAAe,EAAE;AAAA,UAChD,UAAU;AAAA,QACZ;AAAA,QACA,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AACA,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,MAAM,iBAAiB,SAAS,YAAY,SAAS,iCAAiC;AAAA,MACnG,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
6
|
+
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
7
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
8
|
+
import {
|
|
9
|
+
computeEnvFingerprint,
|
|
10
|
+
readOperatorsBlob
|
|
11
|
+
} from "../../lib/operators-store.js";
|
|
12
|
+
import { createTillioLock, tillioOperatorLockKey } from "../../lib/locking.js";
|
|
13
|
+
import { evaluateEnvironmentReadiness } from "../../lib/pull-readiness.js";
|
|
14
|
+
import {
|
|
15
|
+
TillioApiError
|
|
16
|
+
} from "../../lib/errors.js";
|
|
17
|
+
import { tillioErrorCopy } from "../../lib/error-codes.js";
|
|
18
|
+
import {
|
|
19
|
+
attachOperator,
|
|
20
|
+
classifyTillioError,
|
|
21
|
+
readTillioIntegrationState,
|
|
22
|
+
resolveEnvironment,
|
|
23
|
+
TILLIO_OPERATOR_RESOURCE_KIND,
|
|
24
|
+
TillioEnvironmentNotReadyError,
|
|
25
|
+
TillioOperatorLimitError
|
|
26
|
+
} from "../../lib/operators.js";
|
|
27
|
+
const SUPPORTED_PLUGINS = ["Ringostat"];
|
|
28
|
+
const logger = createLogger("tillio").child({ component: "operators-route" });
|
|
29
|
+
const ATTACH_BLOCKER_MESSAGES = {
|
|
30
|
+
integration_disabled: "The Tillio integration is disabled. Enable it before attaching an operator.",
|
|
31
|
+
environment_not_ready: "Run the Tillio environment health check before attaching an operator."
|
|
32
|
+
};
|
|
33
|
+
const attachBodySchema = z.object({
|
|
34
|
+
plugin: z.literal("Ringostat"),
|
|
35
|
+
config: z.object({ key: z.string().trim().min(1) }),
|
|
36
|
+
label: z.string().trim().optional()
|
|
37
|
+
});
|
|
38
|
+
const metadata = {
|
|
39
|
+
GET: { requireAuth: true, requireFeatures: ["tillio.manage", "integrations.manage"] },
|
|
40
|
+
POST: { requireAuth: true, requireFeatures: ["tillio.manage", "integrations.manage"] }
|
|
41
|
+
};
|
|
42
|
+
const openApi = {
|
|
43
|
+
tags: ["Tillio"],
|
|
44
|
+
summary: "List and attach Tillio operators"
|
|
45
|
+
};
|
|
46
|
+
async function GET(req) {
|
|
47
|
+
const auth = await getAuthFromRequest(req);
|
|
48
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
49
|
+
return NextResponse.json({ ok: false, code: "unauthorized", message: "Unauthorized" }, { status: 401 });
|
|
50
|
+
}
|
|
51
|
+
const container = await createRequestContainer();
|
|
52
|
+
const credentialsService = container.resolve("integrationCredentialsService");
|
|
53
|
+
const em = container.resolve("em");
|
|
54
|
+
const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
|
|
55
|
+
const environment = await resolveEnvironment(credentialsService, scope);
|
|
56
|
+
const integrationState = await readTillioIntegrationState(em, scope);
|
|
57
|
+
const { ready: environmentReady, blocker: environmentBlocker } = evaluateEnvironmentReadiness({
|
|
58
|
+
environment,
|
|
59
|
+
integrationEnabled: integrationState.enabled,
|
|
60
|
+
environmentHealthy: integrationState.healthy
|
|
61
|
+
});
|
|
62
|
+
const blob = await readOperatorsBlob(credentialsService, scope);
|
|
63
|
+
const currentFingerprint = environment ? computeEnvFingerprint(environment) : null;
|
|
64
|
+
const operators = blob.operators.map((operator) => ({
|
|
65
|
+
id: operator.id,
|
|
66
|
+
plugin: operator.plugin,
|
|
67
|
+
tenantDomain: operator.tenantDomain,
|
|
68
|
+
stale: currentFingerprint ? operator.envFingerprint !== currentFingerprint : true
|
|
69
|
+
}));
|
|
70
|
+
return NextResponse.json({
|
|
71
|
+
ok: true,
|
|
72
|
+
environmentReady,
|
|
73
|
+
environmentBlocker,
|
|
74
|
+
tenantSystemId: environment?.tenantSystemId ?? null,
|
|
75
|
+
supportedPlugins: SUPPORTED_PLUGINS,
|
|
76
|
+
operators,
|
|
77
|
+
defaultOperatorId: blob.defaultOperatorId,
|
|
78
|
+
envDrift: operators.some((operator) => operator.stale)
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function POST(req) {
|
|
82
|
+
const auth = await getAuthFromRequest(req);
|
|
83
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
84
|
+
return NextResponse.json({ ok: false, code: "unauthorized", message: "Unauthorized" }, { status: 401 });
|
|
85
|
+
}
|
|
86
|
+
const parsedBody = attachBodySchema.safeParse(await readJsonSafe(req));
|
|
87
|
+
if (!parsedBody.success) {
|
|
88
|
+
return NextResponse.json({ ok: false, code: "invalid_payload", message: "Invalid operator payload" }, { status: 400 });
|
|
89
|
+
}
|
|
90
|
+
const container = await createRequestContainer();
|
|
91
|
+
const credentialsService = container.resolve("integrationCredentialsService");
|
|
92
|
+
const em = container.resolve("em");
|
|
93
|
+
const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
|
|
94
|
+
const environment = await resolveEnvironment(credentialsService, scope);
|
|
95
|
+
const integrationState = await readTillioIntegrationState(em, scope);
|
|
96
|
+
const { blocker } = evaluateEnvironmentReadiness({
|
|
97
|
+
environment,
|
|
98
|
+
integrationEnabled: integrationState.enabled,
|
|
99
|
+
environmentHealthy: integrationState.healthy
|
|
100
|
+
});
|
|
101
|
+
if (blocker) {
|
|
102
|
+
return NextResponse.json(
|
|
103
|
+
{ ok: false, code: blocker, section: "environment", message: ATTACH_BLOCKER_MESSAGES[blocker] },
|
|
104
|
+
{ status: 409 }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const appUrl = process.env.APP_URL ?? "";
|
|
108
|
+
if (!appUrl.trim()) {
|
|
109
|
+
return NextResponse.json(
|
|
110
|
+
{ ok: false, code: "app_url_missing", section: "environment", message: "APP_URL is not configured; it is required to derive the operator webhook domain." },
|
|
111
|
+
{ status: 500 }
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const guarded = await runRouteMutationGuards({
|
|
115
|
+
container,
|
|
116
|
+
req,
|
|
117
|
+
auth: { userId: auth.sub, tenantId: auth.tenantId, organizationId: auth.orgId },
|
|
118
|
+
input: {
|
|
119
|
+
resourceKind: TILLIO_OPERATOR_RESOURCE_KIND,
|
|
120
|
+
operation: "create",
|
|
121
|
+
// The operator key is a credential, so it is described to guards, never handed to them.
|
|
122
|
+
mutationPayload: { plugin: parsedBody.data.plugin, label: parsedBody.data.label ?? null }
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
if (!guarded.ok) return guarded.response;
|
|
126
|
+
const guardedLabel = typeof guarded.modifiedPayload?.label === "string" ? guarded.modifiedPayload.label : parsedBody.data.label;
|
|
127
|
+
try {
|
|
128
|
+
const operator = await attachOperator(
|
|
129
|
+
{ credentialsService, scope, appUrl, withLock: createTillioLock(em, tillioOperatorLockKey(scope)) },
|
|
130
|
+
{ plugin: parsedBody.data.plugin, config: parsedBody.data.config, label: guardedLabel }
|
|
131
|
+
);
|
|
132
|
+
await guarded.runAfterSuccess();
|
|
133
|
+
return NextResponse.json({
|
|
134
|
+
ok: true,
|
|
135
|
+
operator: { id: operator.id, plugin: operator.plugin, tenantDomain: operator.tenantDomain }
|
|
136
|
+
});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
if (err instanceof TillioOperatorLimitError) {
|
|
139
|
+
return NextResponse.json({ ok: false, code: "operator_limit", section: "operator", message: err.message }, { status: 409 });
|
|
140
|
+
}
|
|
141
|
+
if (err instanceof TillioEnvironmentNotReadyError) {
|
|
142
|
+
return NextResponse.json({ ok: false, code: "environment_not_ready", section: "environment", message: err.message }, { status: 409 });
|
|
143
|
+
}
|
|
144
|
+
if (err instanceof TillioApiError) {
|
|
145
|
+
const section = classifyTillioError(err);
|
|
146
|
+
logger.error("Tillio operator attach failed at provider", {
|
|
147
|
+
section,
|
|
148
|
+
status: err.status,
|
|
149
|
+
detail: err.detail,
|
|
150
|
+
err
|
|
151
|
+
});
|
|
152
|
+
return NextResponse.json(
|
|
153
|
+
{
|
|
154
|
+
ok: false,
|
|
155
|
+
code: "provider_error",
|
|
156
|
+
section,
|
|
157
|
+
message: tillioErrorCopy("provider_error", "provider_error").fallback
|
|
158
|
+
},
|
|
159
|
+
{ status: section === "environment" ? 502 : 422 }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return NextResponse.json({ ok: false, code: "attach_failed", section: "operator", message: "Failed to attach the operator." }, { status: 500 });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
GET,
|
|
167
|
+
POST,
|
|
168
|
+
metadata,
|
|
169
|
+
openApi
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/tillio/api/operators/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'\nimport {\n computeEnvFingerprint,\n readOperatorsBlob,\n type TillioCredentialsService,\n} from '../../lib/operators-store'\nimport { createTillioLock, tillioOperatorLockKey } from '../../lib/locking'\nimport { evaluateEnvironmentReadiness, type EnvironmentBlocker } from '../../lib/pull-readiness'\nimport {\n TillioApiError,\n} from '../../lib/errors'\nimport { tillioErrorCopy } from '../../lib/error-codes'\nimport {\n attachOperator,\n classifyTillioError,\n readTillioIntegrationState,\n resolveEnvironment,\n TILLIO_OPERATOR_RESOURCE_KIND,\n TillioEnvironmentNotReadyError,\n TillioOperatorLimitError,\n} from '../../lib/operators'\n\nconst SUPPORTED_PLUGINS = ['Ringostat'] as const\nconst logger = createLogger('tillio').child({ component: 'operators-route' })\n\n// Same blockers as the pull reports, worded for the action the caller was attempting.\nconst ATTACH_BLOCKER_MESSAGES: Record<EnvironmentBlocker, string> = {\n integration_disabled: 'The Tillio integration is disabled. Enable it before attaching an operator.',\n environment_not_ready: 'Run the Tillio environment health check before attaching an operator.',\n}\n\nconst attachBodySchema = z.object({\n plugin: z.literal('Ringostat'),\n config: z.object({ key: z.string().trim().min(1) }),\n label: z.string().trim().optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['tillio.manage', 'integrations.manage'] },\n POST: { requireAuth: true, requireFeatures: ['tillio.manage', 'integrations.manage'] },\n}\n\nexport const openApi = {\n tags: ['Tillio'],\n summary: 'List and attach Tillio operators',\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ ok: false, code: 'unauthorized', message: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as TillioCredentialsService\n const em = container.resolve('em') as EntityManager\n const scope: IntegrationScope = { organizationId: auth.orgId, tenantId: auth.tenantId }\n\n const environment = await resolveEnvironment(credentialsService, scope)\n const integrationState = await readTillioIntegrationState(em, scope)\n const { ready: environmentReady, blocker: environmentBlocker } = evaluateEnvironmentReadiness({\n environment,\n integrationEnabled: integrationState.enabled,\n environmentHealthy: integrationState.healthy,\n })\n const blob = await readOperatorsBlob(credentialsService, scope)\n const currentFingerprint = environment ? computeEnvFingerprint(environment) : null\n\n const operators = blob.operators.map((operator) => ({\n id: operator.id,\n plugin: operator.plugin,\n tenantDomain: operator.tenantDomain,\n stale: currentFingerprint ? operator.envFingerprint !== currentFingerprint : true,\n }))\n\n return NextResponse.json({\n ok: true,\n environmentReady,\n environmentBlocker,\n tenantSystemId: environment?.tenantSystemId ?? null,\n supportedPlugins: SUPPORTED_PLUGINS,\n operators,\n defaultOperatorId: blob.defaultOperatorId,\n envDrift: operators.some((operator) => operator.stale),\n })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ ok: false, code: 'unauthorized', message: 'Unauthorized' }, { status: 401 })\n }\n\n const parsedBody = attachBodySchema.safeParse(await readJsonSafe(req))\n if (!parsedBody.success) {\n return NextResponse.json({ ok: false, code: 'invalid_payload', message: 'Invalid operator payload' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as TillioCredentialsService\n const em = container.resolve('em') as EntityManager\n const scope: IntegrationScope = { organizationId: auth.orgId, tenantId: auth.tenantId }\n\n const environment = await resolveEnvironment(credentialsService, scope)\n const integrationState = await readTillioIntegrationState(em, scope)\n // Provisioning an operator registers a config on Tillio's side, so the environment is checked\n // here rather than left to the pull to discover. The decision is the shared one; only the\n // wording is specific to attaching.\n const { blocker } = evaluateEnvironmentReadiness({\n environment,\n integrationEnabled: integrationState.enabled,\n environmentHealthy: integrationState.healthy,\n })\n if (blocker) {\n return NextResponse.json(\n { ok: false, code: blocker, section: 'environment', message: ATTACH_BLOCKER_MESSAGES[blocker] },\n { status: 409 },\n )\n }\n\n const appUrl = process.env.APP_URL ?? ''\n if (!appUrl.trim()) {\n return NextResponse.json(\n { ok: false, code: 'app_url_missing', section: 'environment', message: 'APP_URL is not configured; it is required to derive the operator webhook domain.' },\n { status: 500 },\n )\n }\n\n const guarded = await runRouteMutationGuards({\n container,\n req,\n auth: { userId: auth.sub, tenantId: auth.tenantId, organizationId: auth.orgId },\n input: {\n resourceKind: TILLIO_OPERATOR_RESOURCE_KIND,\n operation: 'create',\n // The operator key is a credential, so it is described to guards, never handed to them.\n mutationPayload: { plugin: parsedBody.data.plugin, label: parsedBody.data.label ?? null },\n },\n })\n if (!guarded.ok) return guarded.response\n\n const guardedLabel = typeof guarded.modifiedPayload?.label === 'string'\n ? guarded.modifiedPayload.label\n : parsedBody.data.label\n\n try {\n const operator = await attachOperator(\n { credentialsService, scope, appUrl, withLock: createTillioLock(em, tillioOperatorLockKey(scope)) },\n { plugin: parsedBody.data.plugin, config: parsedBody.data.config, label: guardedLabel },\n )\n await guarded.runAfterSuccess()\n return NextResponse.json({\n ok: true,\n operator: { id: operator.id, plugin: operator.plugin, tenantDomain: operator.tenantDomain },\n })\n } catch (err) {\n if (err instanceof TillioOperatorLimitError) {\n return NextResponse.json({ ok: false, code: 'operator_limit', section: 'operator', message: err.message }, { status: 409 })\n }\n if (err instanceof TillioEnvironmentNotReadyError) {\n return NextResponse.json({ ok: false, code: 'environment_not_ready', section: 'environment', message: err.message }, { status: 409 })\n }\n if (err instanceof TillioApiError) {\n const section = classifyTillioError(err)\n logger.error('Tillio operator attach failed at provider', {\n section,\n status: err.status,\n detail: err.detail,\n err,\n })\n return NextResponse.json(\n {\n ok: false,\n code: 'provider_error',\n section,\n message: tillioErrorCopy('provider_error', 'provider_error').fallback,\n },\n { status: section === 'environment' ? 502 : 422 },\n )\n }\n return NextResponse.json({ ok: false, code: 'attach_failed', section: 'operator', message: 'Failed to attach the operator.' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|