@endora-commerce/mod-api-keys 0.100.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/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/admin/index.d.ts +37 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +43 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/ApiKeysPage.d.ts +3 -0
- package/dist/admin/pages/ApiKeysPage.d.ts.map +1 -0
- package/dist/admin/pages/ApiKeysPage.js +182 -0
- package/dist/admin/pages/ApiKeysPage.js.map +1 -0
- package/dist/backend/entities/api-key.entity.d.ts +33 -0
- package/dist/backend/entities/api-key.entity.d.ts.map +1 -0
- package/dist/backend/entities/api-key.entity.js +118 -0
- package/dist/backend/entities/api-key.entity.js.map +1 -0
- package/dist/backend/index.d.ts +71 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +57 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +65 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +75 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.d.ts +26 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +51 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/api-key-service.d.ts +72 -0
- package/dist/backend/services/api-key-service.d.ts.map +1 -0
- package/dist/backend/services/api-key-service.js +186 -0
- package/dist/backend/services/api-key-service.js.map +1 -0
- package/dist/manifest.d.ts +169 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +155 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.d.ts +50 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.d.ts.map +1 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.js +117 -0
- package/dist/migrations/20260724T173916_api_keys_distributor_binding.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/api_keys.md +89 -0
- package/i18n/en.json +5 -0
- package/i18n/pl.json +5 -0
- package/package.json +93 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Keys module — manifest backfill (Module Lifecycle, feature 018).
|
|
3
|
+
*
|
|
4
|
+
* Predates the lifecycle system; this manifest is the static record
|
|
5
|
+
* required so the module participates in the registry. No install /
|
|
6
|
+
* uninstall hook today — the module's schema is owned by earlier
|
|
7
|
+
* platform-wide migrations.
|
|
8
|
+
*/
|
|
9
|
+
export declare const manifest: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
dependencies: string[];
|
|
14
|
+
description?: string | undefined;
|
|
15
|
+
acknowledgedDependencies?: {
|
|
16
|
+
moduleId: string;
|
|
17
|
+
port: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}[] | undefined;
|
|
20
|
+
nonBindingDependencies?: {
|
|
21
|
+
moduleId: string;
|
|
22
|
+
name: string;
|
|
23
|
+
kind: "contributes-to" | "degrades-without" | "refuses-without";
|
|
24
|
+
reason: string;
|
|
25
|
+
whenAbsent?: string | undefined;
|
|
26
|
+
}[] | undefined;
|
|
27
|
+
activation?: {
|
|
28
|
+
settingCode: string;
|
|
29
|
+
default: boolean;
|
|
30
|
+
} | {
|
|
31
|
+
nonDeactivatable: true;
|
|
32
|
+
reason: string;
|
|
33
|
+
} | undefined;
|
|
34
|
+
settings?: {
|
|
35
|
+
moduleCode: string;
|
|
36
|
+
groups: {
|
|
37
|
+
code: string;
|
|
38
|
+
name: string;
|
|
39
|
+
salesChannelCodes?: string[] | undefined;
|
|
40
|
+
isSystemProtected?: boolean | undefined;
|
|
41
|
+
}[];
|
|
42
|
+
settings: {
|
|
43
|
+
code: string;
|
|
44
|
+
name: string;
|
|
45
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
46
|
+
defaultValue: unknown;
|
|
47
|
+
description?: string | undefined;
|
|
48
|
+
groupCode?: string | undefined;
|
|
49
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
50
|
+
salesChannelCodes?: string[] | undefined;
|
|
51
|
+
enumOptions?: string[] | undefined;
|
|
52
|
+
configurationType?: string | undefined;
|
|
53
|
+
hidden?: boolean | undefined;
|
|
54
|
+
}[];
|
|
55
|
+
} | undefined;
|
|
56
|
+
i18n?: {
|
|
57
|
+
bundlesDir: string;
|
|
58
|
+
} | undefined;
|
|
59
|
+
docs?: false | {
|
|
60
|
+
dir: string;
|
|
61
|
+
} | undefined;
|
|
62
|
+
demo?: false | {
|
|
63
|
+
summary: string;
|
|
64
|
+
seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
|
|
65
|
+
reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
|
|
66
|
+
after?: readonly string[] | undefined;
|
|
67
|
+
package?: string | undefined;
|
|
68
|
+
} | undefined;
|
|
69
|
+
actions?: {
|
|
70
|
+
id: string;
|
|
71
|
+
labelKey: string;
|
|
72
|
+
icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
|
|
73
|
+
targetRoute: string;
|
|
74
|
+
keywords: string[];
|
|
75
|
+
weight: number;
|
|
76
|
+
descriptionKey?: string | undefined;
|
|
77
|
+
requiredPermission?: string | undefined;
|
|
78
|
+
}[] | undefined;
|
|
79
|
+
permissions?: {
|
|
80
|
+
code: string;
|
|
81
|
+
label: string;
|
|
82
|
+
module?: string | undefined;
|
|
83
|
+
description?: string | undefined;
|
|
84
|
+
requires?: string[] | undefined;
|
|
85
|
+
}[] | undefined;
|
|
86
|
+
transactionalEmails?: {
|
|
87
|
+
code: string;
|
|
88
|
+
name: string;
|
|
89
|
+
variables: {
|
|
90
|
+
key: string;
|
|
91
|
+
label: string;
|
|
92
|
+
sampleValue?: string | undefined;
|
|
93
|
+
description?: string | undefined;
|
|
94
|
+
}[];
|
|
95
|
+
description?: string | undefined;
|
|
96
|
+
group?: string | undefined;
|
|
97
|
+
}[] | undefined;
|
|
98
|
+
capabilities?: string[] | undefined;
|
|
99
|
+
exclusiveCapabilities?: {
|
|
100
|
+
key: string;
|
|
101
|
+
errorCode: string;
|
|
102
|
+
}[] | undefined;
|
|
103
|
+
errorCodes?: {
|
|
104
|
+
code: string;
|
|
105
|
+
tokens?: string[] | undefined;
|
|
106
|
+
}[] | undefined;
|
|
107
|
+
blocks?: {
|
|
108
|
+
name: string;
|
|
109
|
+
labelKey: string;
|
|
110
|
+
category: string;
|
|
111
|
+
contexts: ("invoice" | "email" | "cms" | "newsletter")[];
|
|
112
|
+
fields: Record<string, {
|
|
113
|
+
type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
|
|
114
|
+
label?: string | undefined;
|
|
115
|
+
required?: boolean | undefined;
|
|
116
|
+
options?: {
|
|
117
|
+
label: string;
|
|
118
|
+
value: string | number;
|
|
119
|
+
}[] | undefined;
|
|
120
|
+
refKind?: string | undefined;
|
|
121
|
+
}>;
|
|
122
|
+
descriptionKey?: string | undefined;
|
|
123
|
+
defaultProps?: Record<string, unknown> | undefined;
|
|
124
|
+
responsiveFields?: string[] | undefined;
|
|
125
|
+
previewIcon?: string | undefined;
|
|
126
|
+
weight?: number | undefined;
|
|
127
|
+
}[] | undefined;
|
|
128
|
+
blockCategories?: {
|
|
129
|
+
key: string;
|
|
130
|
+
titleKey: string;
|
|
131
|
+
contexts: ("invoice" | "email" | "cms" | "newsletter")[];
|
|
132
|
+
weight?: number | undefined;
|
|
133
|
+
visible?: boolean | undefined;
|
|
134
|
+
}[] | undefined;
|
|
135
|
+
env?: {
|
|
136
|
+
name: string;
|
|
137
|
+
describes: {
|
|
138
|
+
en: string;
|
|
139
|
+
pl: string;
|
|
140
|
+
};
|
|
141
|
+
requirement: {
|
|
142
|
+
kind: "required";
|
|
143
|
+
} | {
|
|
144
|
+
kind: "requiredWhen";
|
|
145
|
+
input: string;
|
|
146
|
+
equals: string;
|
|
147
|
+
} | {
|
|
148
|
+
kind: "optional";
|
|
149
|
+
without: {
|
|
150
|
+
en: string;
|
|
151
|
+
pl: string;
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
secret: boolean;
|
|
155
|
+
generable: boolean;
|
|
156
|
+
owner: {
|
|
157
|
+
kind: "platform";
|
|
158
|
+
} | {
|
|
159
|
+
kind: "application";
|
|
160
|
+
application: "admin" | "backend" | "storefront";
|
|
161
|
+
} | {
|
|
162
|
+
kind: "module";
|
|
163
|
+
moduleId: string;
|
|
164
|
+
};
|
|
165
|
+
consumers: ("admin" | "backend" | "storefront")[];
|
|
166
|
+
addressOf: "admin" | "backend" | "storefront" | null;
|
|
167
|
+
}[] | undefined;
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkJnB,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { defineModuleManifest } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* API Keys module — manifest backfill (Module Lifecycle, feature 018).
|
|
4
|
+
*
|
|
5
|
+
* Predates the lifecycle system; this manifest is the static record
|
|
6
|
+
* required so the module participates in the registry. No install /
|
|
7
|
+
* uninstall hook today — the module's schema is owned by earlier
|
|
8
|
+
* platform-wide migrations.
|
|
9
|
+
*/
|
|
10
|
+
export const manifest = defineModuleManifest({
|
|
11
|
+
id: 'api_keys',
|
|
12
|
+
name: 'API Keys',
|
|
13
|
+
description: 'Programmatic API keys (Bearer tokens) used by integrations and webhooks.',
|
|
14
|
+
version: '1.0.0',
|
|
15
|
+
// `auth` owns the `requireAdmin` port the admin routes are gated by; feature
|
|
16
|
+
// 072 made it a container resolution rather than a constructor argument.
|
|
17
|
+
dependencies: ['customer_accounts', 'organizations', 'sales_channels', 'auth'],
|
|
18
|
+
settings: {
|
|
19
|
+
moduleCode: 'api_keys',
|
|
20
|
+
groups: [{ code: 'api_keys', name: 'API keys' }],
|
|
21
|
+
settings: [
|
|
22
|
+
{
|
|
23
|
+
// Feature 073 — the operator's activation control. Platform-wide.
|
|
24
|
+
code: 'api_keys.enabled',
|
|
25
|
+
name: 'API keys enabled',
|
|
26
|
+
description: 'Switches machine-to-machine API keys on or off: the admin screens that issue them and the gates that authenticate them on the external catalog namespace. Switched off, existing keys stop authenticating but are not revoked — they work again exactly as issued when you switch it back on.',
|
|
27
|
+
groupCode: 'api_keys',
|
|
28
|
+
valueType: 'boolean',
|
|
29
|
+
defaultValue: true,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
// Issue #213 — `integrations:manage` is a **shared** gate: it guards this
|
|
34
|
+
// module's admin surface and `webhooks`', and the core `PERMISSION_CATALOGUE`
|
|
35
|
+
// row that carries its label can name only one module. Both owners declare it,
|
|
36
|
+
// so the presence filter on `/admin-roles` keeps the code grantable while
|
|
37
|
+
// either surface is on. This half is what the core row already says; it is
|
|
38
|
+
// written out anyway, because a shared code owned by one manifest and one
|
|
39
|
+
// hard-coded core row is the arrangement that produced the asymmetry.
|
|
40
|
+
permissions: [{ code: 'integrations:manage', label: 'Manage API keys + webhooks' }],
|
|
41
|
+
/**
|
|
42
|
+
* The module's command-palette entry — feature 091, Phase 4 (the plan's
|
|
43
|
+
* batch 6), and one of the fifteen Principle XVI entries
|
|
44
|
+
* `specs/deferred-defects.md` records as owed.
|
|
45
|
+
*
|
|
46
|
+
* It arrives with the drain rather than before it, by the mechanism that
|
|
47
|
+
* register predicts: the batch owes an off-state proof over every surface the
|
|
48
|
+
* module contributes, and until this declaration existed the `AppShell.tsx`
|
|
49
|
+
* `PALETTE_ITEMS` row that advertised /api-keys was the admin's own — a
|
|
50
|
+
* hand-written copy no server-side presence check was ever asked about, so it
|
|
51
|
+
* went on offering the screen to an operator who had switched the module off.
|
|
52
|
+
* The Actions group is resolved by `AdminActionsService` against the effective
|
|
53
|
+
* enabled-set, which is what makes the withdrawal real.
|
|
54
|
+
*
|
|
55
|
+
* `requiredPermission` is the code the target route enforces, which
|
|
56
|
+
* `check:action-route-permissions` compares against the registration on
|
|
57
|
+
* `GET /api/v1/admin/api-keys` itself. It is `integrations:manage` and not a
|
|
58
|
+
* `api_keys:`-prefixed code because that is the gate the module actually
|
|
59
|
+
* has: issue #213 records why both owners declare it.
|
|
60
|
+
*/
|
|
61
|
+
actions: [
|
|
62
|
+
{
|
|
63
|
+
id: 'open-api-keys',
|
|
64
|
+
labelKey: 'actions.openApiKeys.label',
|
|
65
|
+
descriptionKey: 'actions.openApiKeys.description',
|
|
66
|
+
icon: 'KeyRound',
|
|
67
|
+
targetRoute: '/api-keys',
|
|
68
|
+
requiredPermission: 'integrations:manage',
|
|
69
|
+
keywords: ['api', 'api keys', 'klucze api', 'bearer', 'token', 'integration', 'integracja'],
|
|
70
|
+
weight: 700,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
activation: { settingCode: 'api_keys.enabled', default: true },
|
|
74
|
+
/**
|
|
75
|
+
* This module's first i18n bundle — D-129's remaining sweep, MR 5 — and it
|
|
76
|
+
* installs **no entries**.
|
|
77
|
+
*
|
|
78
|
+
* It exists because a **declaring** module that contributes no bundle file is
|
|
79
|
+
* `check:error-translations` exit 2 for the whole tree rather than a finding
|
|
80
|
+
* (`specs/090-module-owned-error-codes/contracts/error-translation-population.md`
|
|
81
|
+
* §2.1, §2.3; `d129-sweep.md` §3.4), and all three codes below are ledgered
|
|
82
|
+
* rather than written — so `{}` is what the two files hold. That is the
|
|
83
|
+
* default §5.4 describes for this position, and it is the first time the
|
|
84
|
+
* platform has carried it. What a module in that state does at boot is not
|
|
85
|
+
* decidable from a source-tree check, so the merge request that shipped it
|
|
86
|
+
* ran `bash scripts/boot-gate.sh --with-negatives` and read the answer off a
|
|
87
|
+
* real image: **`installed`**, with two `translation_bundles` rows carrying
|
|
88
|
+
* zero entries — `[i18n] reconcile complete — installed=54 skipped=15
|
|
89
|
+
* failed=0` over the 69 modules the platform composes, none left over.
|
|
90
|
+
* `loadModuleBundles` returns a `Map` of both languages here, not an empty
|
|
91
|
+
* one; §5.4's `{"byLanguage":{}}` is what `JSON.stringify` prints for any
|
|
92
|
+
* `Map` and says nothing about its size.
|
|
93
|
+
*
|
|
94
|
+
* The bundle lives at the **package root**, not under `dist`:
|
|
95
|
+
* `manifest-locations.ts` resolves a packaged module's `manifestPath` to its
|
|
96
|
+
* `package.json`, so `dirname` is the package directory. When a sentence is
|
|
97
|
+
* written here it must be a **flat** `{"a.b.c": "text"}` map, because a nested
|
|
98
|
+
* object fails `TranslationBundleEntriesSchema` and the boot reconciler logs
|
|
99
|
+
* and skips it — silently.
|
|
100
|
+
*/
|
|
101
|
+
i18n: { bundlesDir: 'i18n' },
|
|
102
|
+
docs: { dir: 'docs' },
|
|
103
|
+
/**
|
|
104
|
+
* The `API_KEY_*` codes — D-129's remaining sweep, Tier B
|
|
105
|
+
* (`specs/090-module-owned-error-codes/d129-sweep.md` §5.2, Appendix A;
|
|
106
|
+
* MR 5).
|
|
107
|
+
*
|
|
108
|
+
* All three were declared by `_i18n` until this merge request, not because
|
|
109
|
+
* anybody judged them the platform's but because the deleted prefix chain had
|
|
110
|
+
* no rule for them and its last line was `return 'core'`. **D-121 T1 puts
|
|
111
|
+
* them here**: the noun in each is the API key, which is this module's
|
|
112
|
+
* entity, and its scopes and its distributor binding are columns on that row.
|
|
113
|
+
* T1 does not ask who throws, which matters twice over here — `API_KEY_NOT_BOUND`
|
|
114
|
+
* is raised by three modules (this one, `catalog` and `orders`, each gating
|
|
115
|
+
* its own external namespace) and `API_KEY_CHANNEL_MISMATCH` is raised by the
|
|
116
|
+
* **platform**, in the sales-channel resolver middleware.
|
|
117
|
+
*
|
|
118
|
+
* **That last one is D-186 §3, taken deliberately and with a condition.** A
|
|
119
|
+
* platform raise site now depends on a switchable module's bundle surviving,
|
|
120
|
+
* which is a coupling Phase 3 never created. The ruling accepts it so the
|
|
121
|
+
* family lands in one bundle, and the merge request carries the assertion that
|
|
122
|
+
* makes it safe: the raise is unreachable while `api_keys` is absent, because
|
|
123
|
+
* it needs `request.actor.kind === 'api_key'` and the only thing that produces
|
|
124
|
+
* such an actor is `auth`'s request hook calling this module's gated
|
|
125
|
+
* `apiKeyResolver` — which its own presence probe answers `null` for while
|
|
126
|
+
* this module is off. `test/integration/_lifecycle/non-binding-degradation.integration.test.ts`
|
|
127
|
+
* asserts it with a bound key and a mismatching `x-sales-channel` header.
|
|
128
|
+
*
|
|
129
|
+
* **None of the three arrives with a sentence.** `API_KEY_CHANNEL_MISMATCH`
|
|
130
|
+
* and `API_KEY_NOT_BOUND` never had one and stay on
|
|
131
|
+
* `UNTRANSLATED_ERROR_CODES`, under this module instead of under `_i18n`.
|
|
132
|
+
* `API_KEY_OUT_OF_SCOPE` carried a placeholder — `"Api Key Out Of Scope."` /
|
|
133
|
+
* `"Błąd: api key out of scope."` — which D-186 §2 deletes rather than
|
|
134
|
+
* carries, and it is **not** rewritten: it is the first code in the sweep
|
|
135
|
+
* ledgered despite a live raise site. Two reasons, and the second is the one
|
|
136
|
+
* that decides it. Its reader is an integration rather than a person, so a
|
|
137
|
+
* translation is answered to a program. And the raise names the scope the key
|
|
138
|
+
* is missing — `API key lacks the required scope: <scope>.` — which a fixed
|
|
139
|
+
* sentence would replace with a vaguer one, because
|
|
140
|
+
* `localizeErrorEnvelope` substitutes the message wholesale and the raise
|
|
141
|
+
* passes no `details` for a placeholder to be filled from. Writing one here
|
|
142
|
+
* would take information away from the only audience that meets it.
|
|
143
|
+
*
|
|
144
|
+
* **`tokens` is derived from the raise sites, not from the bundle**
|
|
145
|
+
* (runbook §5), and there are none: every raise is a bare
|
|
146
|
+
* `HttpError(403, code, message)` with no `details` argument, measured by
|
|
147
|
+
* balanced-paren extraction of each call's own arguments.
|
|
148
|
+
*/
|
|
149
|
+
errorCodes: [
|
|
150
|
+
{ code: 'API_KEY_CHANNEL_MISMATCH' },
|
|
151
|
+
{ code: 'API_KEY_NOT_BOUND' },
|
|
152
|
+
{ code: 'API_KEY_OUT_OF_SCOPE' },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;IAC3C,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,UAAU;IAChB,WAAW,EACT,0EAA0E;IAC5E,OAAO,EAAE,OAAO;IAChB,6EAA6E;IAC7E,yEAAyE;IACzE,YAAY,EAAE,CAAC,mBAAmB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,CAAC;IAC9E,QAAQ,EAAE;QACR,UAAU,EAAE,UAAU;QACtB,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAChD,QAAQ,EAAE;YACR;gBACE,kEAAkE;gBAClE,IAAI,EAAE,kBAAkB;gBACxB,IAAI,EAAE,kBAAkB;gBACxB,WAAW,EACT,+RAA+R;gBACjS,SAAS,EAAE,UAAU;gBACrB,SAAS,EAAE,SAAS;gBACpB,YAAY,EAAE,IAAI;aACnB;SACF;KACF;IACD,0EAA0E;IAC1E,8EAA8E;IAC9E,+EAA+E;IAC/E,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,sEAAsE;IACtE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC;IACnF;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,EAAE;QACP;YACE,EAAE,EAAE,eAAe;YACnB,QAAQ,EAAE,2BAA2B;YACrC,cAAc,EAAE,iCAAiC;YACjD,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,WAAW;YACxB,kBAAkB,EAAE,qBAAqB;YACzC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,CAAC;YAC3F,MAAM,EAAE,GAAG;SACZ;KACF;IACD,UAAU,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC9D;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;IAC5B,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CG;IACH,UAAU,EAAE;QACV,EAAE,IAAI,EAAE,0BAA0B,EAAE;QACpC,EAAE,IAAI,EAAE,mBAAmB,EAAE;QAC7B,EAAE,IAAI,EAAE,sBAAsB,EAAE;KACjC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 062 — Distributor API key binding (data-model.md §1), and since
|
|
4
|
+
* `specs/120-migration-closure-bridge-ownership/` Phase 3 the **creation** of
|
|
5
|
+
* `api_keys` itself.
|
|
6
|
+
*
|
|
7
|
+
* The table was created by `webhooks`' frozen
|
|
8
|
+
* `Migration20260425T091359WebhooksUs7Init`: the two modules were one surface
|
|
9
|
+
* until the US7 split, and the creation stayed behind while the entity, the
|
|
10
|
+
* service and every other statement came here. An instance installing
|
|
11
|
+
* `api_keys` without `webhooks` therefore had no migration that builds the
|
|
12
|
+
* table, and this very migration named a table outside its own closure (D-226).
|
|
13
|
+
*
|
|
14
|
+
* **It moved from one frozen body into another, and that is the whole of
|
|
15
|
+
* FR-014.** D-226's prescribed repair — subtract the statement and re-add it
|
|
16
|
+
* above `BASELINE_THROUGH` — is right for a *reference*, which can only move
|
|
17
|
+
* strictly later in the computed order. It is wrong for a *creation*: an
|
|
18
|
+
* above-watermark migration runs after the entire frozen prefix, and two frozen
|
|
19
|
+
* migrations reference `api_keys` — this one's own `alter table` below, and
|
|
20
|
+
* `Migration20260724T193611OrdersOrderPlacementIntents`' foreign key. The
|
|
21
|
+
* creation would have landed after both and broken a fresh database. This
|
|
22
|
+
* migration's historical position precedes both, so it is where the creation
|
|
23
|
+
* goes. Both class names are unchanged and `BASELINE_MIGRATIONS` is untouched:
|
|
24
|
+
* `mikro_orm_migrations` persists the class name and no checksum, so neither
|
|
25
|
+
* edited body is re-offered to a database that has applied it.
|
|
26
|
+
*
|
|
27
|
+
* **`if not exists`, and it is load-bearing rather than defensive.** A database
|
|
28
|
+
* that applied the April migration and has not yet reached this one — anything
|
|
29
|
+
* behind by a release — already has `api_keys` while this migration is pending.
|
|
30
|
+
* A verbatim `create table` would fail there. With the guard it is a no-op on
|
|
31
|
+
* that database and the creation on a fresh one.
|
|
32
|
+
*
|
|
33
|
+
* Adds the distributor binding columns to `api_keys`:
|
|
34
|
+
* - `organization_id` / `sales_channel_id` / `customer_account_id` — the
|
|
35
|
+
* all-or-none binding that pins a key to one org, one channel, and one
|
|
36
|
+
* designated service customer account. `ON DELETE RESTRICT`: revoke keys
|
|
37
|
+
* before removing the bound rows.
|
|
38
|
+
* - `expires_at` — optional expiry; NULL = non-expiring. `authenticate()`
|
|
39
|
+
* refuses past instants.
|
|
40
|
+
*
|
|
41
|
+
* Existing rows keep all four columns NULL ⇒ unbound legacy keys, behavior
|
|
42
|
+
* unchanged (FR-020). The cross-row rule "service account belongs to the bound
|
|
43
|
+
* org" is enforced at creation in `ApiKeyService.create` (research §R4), not
|
|
44
|
+
* as a DB constraint.
|
|
45
|
+
*/
|
|
46
|
+
export declare class Migration20260724T173916ApiKeysDistributorBinding extends Migration {
|
|
47
|
+
up(): Promise<void>;
|
|
48
|
+
down(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=20260724T173916_api_keys_distributor_binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260724T173916_api_keys_distributor_binding.d.ts","sourceRoot":"","sources":["../../src/migrations/20260724T173916_api_keys_distributor_binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,iDAAkD,SAAQ,SAAS;IAC/D,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IA4DnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAqBrC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 062 — Distributor API key binding (data-model.md §1), and since
|
|
4
|
+
* `specs/120-migration-closure-bridge-ownership/` Phase 3 the **creation** of
|
|
5
|
+
* `api_keys` itself.
|
|
6
|
+
*
|
|
7
|
+
* The table was created by `webhooks`' frozen
|
|
8
|
+
* `Migration20260425T091359WebhooksUs7Init`: the two modules were one surface
|
|
9
|
+
* until the US7 split, and the creation stayed behind while the entity, the
|
|
10
|
+
* service and every other statement came here. An instance installing
|
|
11
|
+
* `api_keys` without `webhooks` therefore had no migration that builds the
|
|
12
|
+
* table, and this very migration named a table outside its own closure (D-226).
|
|
13
|
+
*
|
|
14
|
+
* **It moved from one frozen body into another, and that is the whole of
|
|
15
|
+
* FR-014.** D-226's prescribed repair — subtract the statement and re-add it
|
|
16
|
+
* above `BASELINE_THROUGH` — is right for a *reference*, which can only move
|
|
17
|
+
* strictly later in the computed order. It is wrong for a *creation*: an
|
|
18
|
+
* above-watermark migration runs after the entire frozen prefix, and two frozen
|
|
19
|
+
* migrations reference `api_keys` — this one's own `alter table` below, and
|
|
20
|
+
* `Migration20260724T193611OrdersOrderPlacementIntents`' foreign key. The
|
|
21
|
+
* creation would have landed after both and broken a fresh database. This
|
|
22
|
+
* migration's historical position precedes both, so it is where the creation
|
|
23
|
+
* goes. Both class names are unchanged and `BASELINE_MIGRATIONS` is untouched:
|
|
24
|
+
* `mikro_orm_migrations` persists the class name and no checksum, so neither
|
|
25
|
+
* edited body is re-offered to a database that has applied it.
|
|
26
|
+
*
|
|
27
|
+
* **`if not exists`, and it is load-bearing rather than defensive.** A database
|
|
28
|
+
* that applied the April migration and has not yet reached this one — anything
|
|
29
|
+
* behind by a release — already has `api_keys` while this migration is pending.
|
|
30
|
+
* A verbatim `create table` would fail there. With the guard it is a no-op on
|
|
31
|
+
* that database and the creation on a fresh one.
|
|
32
|
+
*
|
|
33
|
+
* Adds the distributor binding columns to `api_keys`:
|
|
34
|
+
* - `organization_id` / `sales_channel_id` / `customer_account_id` — the
|
|
35
|
+
* all-or-none binding that pins a key to one org, one channel, and one
|
|
36
|
+
* designated service customer account. `ON DELETE RESTRICT`: revoke keys
|
|
37
|
+
* before removing the bound rows.
|
|
38
|
+
* - `expires_at` — optional expiry; NULL = non-expiring. `authenticate()`
|
|
39
|
+
* refuses past instants.
|
|
40
|
+
*
|
|
41
|
+
* Existing rows keep all four columns NULL ⇒ unbound legacy keys, behavior
|
|
42
|
+
* unchanged (FR-020). The cross-row rule "service account belongs to the bound
|
|
43
|
+
* org" is enforced at creation in `ApiKeyService.create` (research §R4), not
|
|
44
|
+
* as a DB constraint.
|
|
45
|
+
*/
|
|
46
|
+
export class Migration20260724T173916ApiKeysDistributorBinding extends Migration {
|
|
47
|
+
async up() {
|
|
48
|
+
this.addSql(`
|
|
49
|
+
create table if not exists "api_keys" (
|
|
50
|
+
"id" uuid not null,
|
|
51
|
+
"name" varchar(160) not null,
|
|
52
|
+
"key_hash" varchar(128) not null,
|
|
53
|
+
"last_four" varchar(8) not null,
|
|
54
|
+
"scopes" jsonb not null default '[]'::jsonb,
|
|
55
|
+
"status" varchar(16) not null default 'active',
|
|
56
|
+
"last_used_at" timestamptz null,
|
|
57
|
+
"revoked_at" timestamptz null,
|
|
58
|
+
"created_by_admin_user_id" uuid null,
|
|
59
|
+
"created_at" timestamptz not null,
|
|
60
|
+
"updated_at" timestamptz not null,
|
|
61
|
+
constraint "api_keys_pkey" primary key ("id"),
|
|
62
|
+
constraint "api_keys_key_hash_unique" unique ("key_hash")
|
|
63
|
+
);
|
|
64
|
+
`);
|
|
65
|
+
this.addSql('create index if not exists "api_keys_key_hash_index" on "api_keys" ("key_hash");');
|
|
66
|
+
this.addSql(`
|
|
67
|
+
alter table "api_keys"
|
|
68
|
+
add column "organization_id" uuid null,
|
|
69
|
+
add column "sales_channel_id" uuid null,
|
|
70
|
+
add column "customer_account_id" uuid null,
|
|
71
|
+
add column "expires_at" timestamptz null;
|
|
72
|
+
`);
|
|
73
|
+
this.addSql(`
|
|
74
|
+
alter table "api_keys"
|
|
75
|
+
add constraint "api_keys_organization_id_foreign"
|
|
76
|
+
foreign key ("organization_id") references "organizations" ("id")
|
|
77
|
+
on update cascade on delete restrict;
|
|
78
|
+
`);
|
|
79
|
+
this.addSql(`
|
|
80
|
+
alter table "api_keys"
|
|
81
|
+
add constraint "api_keys_sales_channel_id_foreign"
|
|
82
|
+
foreign key ("sales_channel_id") references "sales_channels" ("id")
|
|
83
|
+
on update cascade on delete restrict;
|
|
84
|
+
`);
|
|
85
|
+
this.addSql(`
|
|
86
|
+
alter table "api_keys"
|
|
87
|
+
add constraint "api_keys_customer_account_id_foreign"
|
|
88
|
+
foreign key ("customer_account_id") references "customer_accounts" ("id")
|
|
89
|
+
on update cascade on delete restrict;
|
|
90
|
+
`);
|
|
91
|
+
this.addSql(`
|
|
92
|
+
alter table "api_keys"
|
|
93
|
+
add constraint "api_keys_binding_all_or_none" check (
|
|
94
|
+
("organization_id" is null and "sales_channel_id" is null and "customer_account_id" is null)
|
|
95
|
+
or
|
|
96
|
+
("organization_id" is not null and "sales_channel_id" is not null and "customer_account_id" is not null)
|
|
97
|
+
);
|
|
98
|
+
`);
|
|
99
|
+
this.addSql(`create index "api_keys_organization_id_index" on "api_keys" ("organization_id");`);
|
|
100
|
+
}
|
|
101
|
+
async down() {
|
|
102
|
+
this.addSql(`drop index if exists "api_keys_organization_id_index";`);
|
|
103
|
+
this.addSql(`alter table "api_keys" drop constraint if exists "api_keys_binding_all_or_none";`);
|
|
104
|
+
this.addSql(`alter table "api_keys" drop constraint if exists "api_keys_customer_account_id_foreign";`);
|
|
105
|
+
this.addSql(`alter table "api_keys" drop constraint if exists "api_keys_sales_channel_id_foreign";`);
|
|
106
|
+
this.addSql(`alter table "api_keys" drop constraint if exists "api_keys_organization_id_foreign";`);
|
|
107
|
+
this.addSql(`
|
|
108
|
+
alter table "api_keys"
|
|
109
|
+
drop column "organization_id",
|
|
110
|
+
drop column "sales_channel_id",
|
|
111
|
+
drop column "customer_account_id",
|
|
112
|
+
drop column "expires_at";
|
|
113
|
+
`);
|
|
114
|
+
this.addSql('drop table if exists "api_keys" cascade;');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=20260724T173916_api_keys_distributor_binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260724T173916_api_keys_distributor_binding.js","sourceRoot":"","sources":["../../src/migrations/20260724T173916_api_keys_distributor_binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,OAAO,iDAAkD,SAAQ,SAAS;IACrE,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;KAgBX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,kFAAkF,CACnF,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC;;;;;;KAMX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;;KAKX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;;KAKX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;;KAKX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;;;;KAOX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,kFAAkF,CACnF,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,wDAAwD,CAAC,CAAC;QACtE,IAAI,CAAC,MAAM,CAAC,kFAAkF,CAAC,CAAC;QAChG,IAAI,CAAC,MAAM,CACT,0FAA0F,CAC3F,CAAC;QACF,IAAI,CAAC,MAAM,CACT,uFAAuF,CACxF,CAAC;QACF,IAAI,CAAC,MAAM,CACT,sFAAsF,CACvF,CAAC;QACF,IAAI,CAAC,MAAM,CAAC;;;;;;KAMX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,0CAA0C,CAAC,CAAC;IAC1D,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which is the order of this module's own
|
|
10
|
+
* migrations and of nothing else (feature 081): a manifest `dependencies` array
|
|
11
|
+
* is the only thing ordering this block against another module's.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay beside the array, and the asymmetry with
|
|
14
|
+
* `./backend` — which publishes an array and no named class (D-168) — is
|
|
15
|
+
* deliberate. `db/migrations-registry.generated.ts` imports each class by name
|
|
16
|
+
* from this specifier, and a migration class name is contract in a way an entity
|
|
17
|
+
* class name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom is
|
|
22
|
+
* a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260724T173916ApiKeysDistributorBinding } from './20260724T173916_api_keys_distributor_binding.js';
|
|
25
|
+
export declare const migrations: (typeof Migration20260724T173916ApiKeysDistributorBinding)[];
|
|
26
|
+
export { Migration20260724T173916ApiKeysDistributorBinding, };
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,iDAAiD,EAAE,MAAM,mDAAmD,CAAC;AAEtH,eAAO,MAAM,UAAU,8DAEtB,CAAC;AAEF,OAAO,EACL,iDAAiD,GAClD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which is the order of this module's own
|
|
10
|
+
* migrations and of nothing else (feature 081): a manifest `dependencies` array
|
|
11
|
+
* is the only thing ordering this block against another module's.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay beside the array, and the asymmetry with
|
|
14
|
+
* `./backend` — which publishes an array and no named class (D-168) — is
|
|
15
|
+
* deliberate. `db/migrations-registry.generated.ts` imports each class by name
|
|
16
|
+
* from this specifier, and a migration class name is contract in a way an entity
|
|
17
|
+
* class name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom is
|
|
22
|
+
* a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260724T173916ApiKeysDistributorBinding } from './20260724T173916_api_keys_distributor_binding.js';
|
|
25
|
+
export const migrations = [
|
|
26
|
+
Migration20260724T173916ApiKeysDistributorBinding,
|
|
27
|
+
];
|
|
28
|
+
export { Migration20260724T173916ApiKeysDistributorBinding, };
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,iDAAiD,EAAE,MAAM,mDAAmD,CAAC;AAEtH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,iDAAiD;CAClD,CAAC;AAEF,OAAO,EACL,iDAAiD,GAClD,CAAC"}
|