@wtfalch/foundry 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/bootstrap.d.ts +16 -1
- package/dist/bootstrap/bootstrap.js +40 -2
- package/dist/bootstrap/declare.js +6 -1
- package/dist/calls-sfu-deploy-support.d.ts +8 -0
- package/dist/calls-sfu-deploy-support.js +10 -1
- package/dist/calls-sfu.d.ts +31 -8
- package/dist/calls-sfu.js +36 -5
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -2
- package/dist/migrations/0019_mail_owner_mailbox.sql +15 -0
- package/dist/migrations/0020_mail_apps.sql +7 -0
- package/dist/migrations/0021_mail_role_mailboxes.sql +6 -0
- package/dist/org.d.ts +81 -0
- package/dist/org.js +88 -0
- package/dist/plan/diff.js +93 -10
- package/dist/plan/executor.d.ts +236 -35
- package/dist/plan/executor.js +274 -76
- package/dist/plan/index.d.ts +7 -1
- package/dist/plan/index.js +4 -1
- package/dist/plan/mail-copy.d.ts +125 -0
- package/dist/plan/mail-copy.js +166 -0
- package/dist/plan/mail-identity-allowlist.d.ts +50 -0
- package/dist/plan/mail-identity-allowlist.js +11 -0
- package/dist/plan/mail-identity.d.ts +68 -0
- package/dist/plan/mail-identity.js +17 -0
- package/dist/plan/plan-apply.d.ts +5 -0
- package/dist/plan/platform-diff.d.ts +184 -9
- package/dist/plan/platform-diff.js +368 -15
- package/dist/plan/platform-org.d.ts +3 -2
- package/dist/plan/platform-providers.d.ts +138 -24
- package/dist/plan/platform-providers.js +1460 -79
- package/dist/plan/platform-sfu.d.ts +54 -19
- package/dist/plan/platform-sfu.js +64 -22
- package/dist/plan/zitadel-machine-user.d.ts +60 -11
- package/dist/plan/zitadel-machine-user.js +182 -39
- package/dist/requested-changes.d.ts +17 -7
- package/dist/requested-changes.js +37 -7
- package/dist/store/postgres.js +31 -2
- package/dist/store/schema.d.ts +72 -0
- package/dist/store/schema.js +13 -0
- package/dist/store.d.ts +7 -0
- package/package.json +1 -1
|
@@ -53,12 +53,37 @@ function priceChanges(_before, _after) {
|
|
|
53
53
|
return false;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
57
|
-
* `
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
56
|
+
* Whether a `StorageConfig`/`AiConfig` limit field moved up from `before` to
|
|
57
|
+
* `after` -- both are `null`-or-bigint-string, `null` meaning uncapped
|
|
58
|
+
* (`org.ts`'s `StorageConfig`/`AiConfig` doc). `null` is the largest
|
|
59
|
+
* possible limit, so moving a capped `before` to an uncapped `after` is a
|
|
60
|
+
* raise; moving an already-uncapped `before` anywhere is never a raise
|
|
61
|
+
* (there is nothing higher to move to); two finite values compare as
|
|
62
|
+
* bigints, never as numbers, the same bigint-safety `org.ts` requires of
|
|
63
|
+
* these fields. Equal values are not a raise.
|
|
64
|
+
*/
|
|
65
|
+
function isLimitRaise(before, after) {
|
|
66
|
+
if (before === null)
|
|
67
|
+
return false;
|
|
68
|
+
if (after === null)
|
|
69
|
+
return true;
|
|
70
|
+
return BigInt(after) > BigInt(before);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* `auto` when `change` is small and safe (`calls`, and `storageLimit`/
|
|
74
|
+
* `aiLimit` in the lowering or unchanged direction), the plan it causes has
|
|
75
|
+
* no destructive or migrating step, and it causes no price change on
|
|
76
|
+
* `org`'s own billing fields; `approval` otherwise -- every `domain` or
|
|
77
|
+
* `mailTier` change classifies `approval` unconditionally, since #178
|
|
78
|
+
* "Decide" calls both migrating steps that always need approval, whatever
|
|
79
|
+
* direction the change moves.
|
|
80
|
+
*
|
|
81
|
+
* A `storageLimit` or `aiLimit` change that *raises* either limit
|
|
82
|
+
* ({@link isLimitRaise} against `org`'s current value) classifies
|
|
83
|
+
* `approval`, decided in #184: "a raise of the storage or AI limit
|
|
84
|
+
* classifies `approval` until billing fields exist. A lowering stays
|
|
85
|
+
* `auto`." `storageLimit` carries two independent limits (`limitBytes`,
|
|
86
|
+
* `limitMicros`); a raise on either one is enough.
|
|
62
87
|
*
|
|
63
88
|
* `plan` is the {@link PlanDetail} `change` causes -- the caller's own diff
|
|
64
89
|
* against live reality, from the same `computePlanDetail` (`plan/diff.ts`)
|
|
@@ -85,9 +110,14 @@ export function classifyChange(org, change, plan) {
|
|
|
85
110
|
return 'approval';
|
|
86
111
|
switch (change.field) {
|
|
87
112
|
case 'calls':
|
|
113
|
+
return 'auto';
|
|
88
114
|
case 'storageLimit':
|
|
115
|
+
return isLimitRaise(org.storage.limitBytes, change.limitBytes) ||
|
|
116
|
+
isLimitRaise(org.storage.limitMicros, change.limitMicros)
|
|
117
|
+
? 'approval'
|
|
118
|
+
: 'auto';
|
|
89
119
|
case 'aiLimit':
|
|
90
|
-
return 'auto';
|
|
120
|
+
return isLimitRaise(org.ai.limitMicros, change.limitMicros) ? 'approval' : 'auto';
|
|
91
121
|
case 'domain':
|
|
92
122
|
case 'mailTier':
|
|
93
123
|
return 'approval';
|
package/dist/store/postgres.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asc, eq, sql } from 'drizzle-orm';
|
|
2
|
-
import { validateBuyerIdentity, validateDomain, validateHosting, validateOwner, validateSite, } from '../org.js';
|
|
2
|
+
import { DEFAULT_MAIL_APPS, DEFAULT_NO_REPLY_LOCAL_PART, validateBuyerIdentity, validateDomain, validateHosting, validateMail, validateOwner, validateSite, } from '../org.js';
|
|
3
3
|
import { validatePlatform } from '../platform.js';
|
|
4
4
|
import { RequestedChangeError, classifyChange } from '../requested-changes.js';
|
|
5
5
|
import { validateSlug } from '../slug.js';
|
|
@@ -59,7 +59,14 @@ function rowToOrg(row) {
|
|
|
59
59
|
slug: row.slug,
|
|
60
60
|
displayName: row.displayName,
|
|
61
61
|
domain: row.domain,
|
|
62
|
-
mail: {
|
|
62
|
+
mail: {
|
|
63
|
+
mode: row.mailMode,
|
|
64
|
+
usesDomain: row.mailUsesDomain,
|
|
65
|
+
ownerMailboxAddress: row.ownerMailboxAddress,
|
|
66
|
+
noReplyLocalPart: row.noReplyLocalPart,
|
|
67
|
+
mailApps: row.mailApps,
|
|
68
|
+
roleMailboxes: row.roleMailboxes,
|
|
69
|
+
},
|
|
63
70
|
chat: { usesDomain: row.chatUsesDomain },
|
|
64
71
|
calls: { enabled: row.callsEnabled, usesDomain: row.callsUsesDomain },
|
|
65
72
|
issuer: { usesDomain: row.issuerUsesDomain },
|
|
@@ -96,6 +103,10 @@ function orgToRow(org) {
|
|
|
96
103
|
domain: org.domain,
|
|
97
104
|
mailMode: org.mail.mode,
|
|
98
105
|
mailUsesDomain: org.mail.usesDomain,
|
|
106
|
+
ownerMailboxAddress: org.mail.ownerMailboxAddress ?? null,
|
|
107
|
+
noReplyLocalPart: org.mail.noReplyLocalPart ?? DEFAULT_NO_REPLY_LOCAL_PART,
|
|
108
|
+
mailApps: org.mail.mailApps ?? [...DEFAULT_MAIL_APPS],
|
|
109
|
+
roleMailboxes: org.mail.roleMailboxes ?? [],
|
|
99
110
|
chatUsesDomain: org.chat.usesDomain,
|
|
100
111
|
callsEnabled: org.calls.enabled,
|
|
101
112
|
callsUsesDomain: org.calls.usesDomain,
|
|
@@ -256,6 +267,16 @@ export function createPostgresDeclaredOrgStore(db) {
|
|
|
256
267
|
if (reason)
|
|
257
268
|
throw new DeclaredOrgError(`owner invalid: ${reason}`);
|
|
258
269
|
}
|
|
270
|
+
// #162: same place `requireValidOwner` checks the owner, on both `create`
|
|
271
|
+
// and the merged result of `update`. Deliberately does not require
|
|
272
|
+
// `mail.ownerMailboxAddress` just because mail is on -- `validateMail`'s
|
|
273
|
+
// own doc comment says why. Also covers #34's `mailApps` check, folded
|
|
274
|
+
// into the same `validateMail`.
|
|
275
|
+
function requireValidMail(org) {
|
|
276
|
+
const reason = validateMail(org);
|
|
277
|
+
if (reason)
|
|
278
|
+
throw new DeclaredOrgError(`mail invalid: ${reason} (#162)`);
|
|
279
|
+
}
|
|
259
280
|
return {
|
|
260
281
|
async create(org) {
|
|
261
282
|
requireValidSlug(org.slug);
|
|
@@ -265,6 +286,7 @@ export function createPostgresDeclaredOrgStore(db) {
|
|
|
265
286
|
requireValidHosting(org);
|
|
266
287
|
requireValidSite(org);
|
|
267
288
|
requireValidOwner(org);
|
|
289
|
+
requireValidMail(org);
|
|
268
290
|
try {
|
|
269
291
|
await db.insert(declaredOrgs).values(orgToRow(org));
|
|
270
292
|
}
|
|
@@ -289,6 +311,12 @@ export function createPostgresDeclaredOrgStore(db) {
|
|
|
289
311
|
const [row] = await db.select().from(declaredOrgs).where(eq(declaredOrgs.id, id)).limit(1);
|
|
290
312
|
return row ? rowToOrg(row) : null;
|
|
291
313
|
},
|
|
314
|
+
// #133: ordered by display name, the same `asc(...)` shape
|
|
315
|
+
// `createPostgresRequestedChangeStore.listForOrg` already uses below.
|
|
316
|
+
async list() {
|
|
317
|
+
const rows = await db.select().from(declaredOrgs).orderBy(asc(declaredOrgs.displayName));
|
|
318
|
+
return rows.map(rowToOrg);
|
|
319
|
+
},
|
|
292
320
|
async slugTaken(slug) {
|
|
293
321
|
const [row] = await db
|
|
294
322
|
.select({ id: declaredOrgs.id })
|
|
@@ -338,6 +366,7 @@ export function createPostgresDeclaredOrgStore(db) {
|
|
|
338
366
|
requireValidHosting(next);
|
|
339
367
|
requireValidSite(next);
|
|
340
368
|
requireValidOwner(next);
|
|
369
|
+
requireValidMail(next);
|
|
341
370
|
try {
|
|
342
371
|
await db
|
|
343
372
|
.update(declaredOrgs)
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -113,6 +113,78 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
|
|
|
113
113
|
identity: undefined;
|
|
114
114
|
generated: undefined;
|
|
115
115
|
}, {}, {}>;
|
|
116
|
+
ownerMailboxAddress: import("drizzle-orm/pg-core").PgColumn<{
|
|
117
|
+
name: "owner_mailbox_address";
|
|
118
|
+
tableName: "foundry_declared_orgs";
|
|
119
|
+
dataType: "string";
|
|
120
|
+
columnType: "PgText";
|
|
121
|
+
data: string;
|
|
122
|
+
driverParam: string;
|
|
123
|
+
notNull: false;
|
|
124
|
+
hasDefault: false;
|
|
125
|
+
isPrimaryKey: false;
|
|
126
|
+
isAutoincrement: false;
|
|
127
|
+
hasRuntimeDefault: false;
|
|
128
|
+
enumValues: [string, ...string[]];
|
|
129
|
+
baseColumn: never;
|
|
130
|
+
identity: undefined;
|
|
131
|
+
generated: undefined;
|
|
132
|
+
}, {}, {}>;
|
|
133
|
+
noReplyLocalPart: import("drizzle-orm/pg-core").PgColumn<{
|
|
134
|
+
name: "no_reply_local_part";
|
|
135
|
+
tableName: "foundry_declared_orgs";
|
|
136
|
+
dataType: "string";
|
|
137
|
+
columnType: "PgText";
|
|
138
|
+
data: string;
|
|
139
|
+
driverParam: string;
|
|
140
|
+
notNull: true;
|
|
141
|
+
hasDefault: true;
|
|
142
|
+
isPrimaryKey: false;
|
|
143
|
+
isAutoincrement: false;
|
|
144
|
+
hasRuntimeDefault: false;
|
|
145
|
+
enumValues: [string, ...string[]];
|
|
146
|
+
baseColumn: never;
|
|
147
|
+
identity: undefined;
|
|
148
|
+
generated: undefined;
|
|
149
|
+
}, {}, {}>;
|
|
150
|
+
mailApps: import("drizzle-orm/pg-core").PgColumn<{
|
|
151
|
+
name: "mail_apps";
|
|
152
|
+
tableName: "foundry_declared_orgs";
|
|
153
|
+
dataType: "json";
|
|
154
|
+
columnType: "PgJsonb";
|
|
155
|
+
data: string[];
|
|
156
|
+
driverParam: unknown;
|
|
157
|
+
notNull: true;
|
|
158
|
+
hasDefault: true;
|
|
159
|
+
isPrimaryKey: false;
|
|
160
|
+
isAutoincrement: false;
|
|
161
|
+
hasRuntimeDefault: false;
|
|
162
|
+
enumValues: undefined;
|
|
163
|
+
baseColumn: never;
|
|
164
|
+
identity: undefined;
|
|
165
|
+
generated: undefined;
|
|
166
|
+
}, {}, {
|
|
167
|
+
$type: string[];
|
|
168
|
+
}>;
|
|
169
|
+
roleMailboxes: import("drizzle-orm/pg-core").PgColumn<{
|
|
170
|
+
name: "role_mailboxes";
|
|
171
|
+
tableName: "foundry_declared_orgs";
|
|
172
|
+
dataType: "json";
|
|
173
|
+
columnType: "PgJsonb";
|
|
174
|
+
data: string[];
|
|
175
|
+
driverParam: unknown;
|
|
176
|
+
notNull: true;
|
|
177
|
+
hasDefault: true;
|
|
178
|
+
isPrimaryKey: false;
|
|
179
|
+
isAutoincrement: false;
|
|
180
|
+
hasRuntimeDefault: false;
|
|
181
|
+
enumValues: undefined;
|
|
182
|
+
baseColumn: never;
|
|
183
|
+
identity: undefined;
|
|
184
|
+
generated: undefined;
|
|
185
|
+
}, {}, {
|
|
186
|
+
$type: string[];
|
|
187
|
+
}>;
|
|
116
188
|
chatUsesDomain: import("drizzle-orm/pg-core").PgColumn<{
|
|
117
189
|
name: "chat_uses_domain";
|
|
118
190
|
tableName: "foundry_declared_orgs";
|
package/dist/store/schema.js
CHANGED
|
@@ -12,6 +12,19 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
|
|
|
12
12
|
domain: text('domain'),
|
|
13
13
|
mailMode: text('mail_mode').notNull().default('off'),
|
|
14
14
|
mailUsesDomain: boolean('mail_uses_domain').notNull().default(false),
|
|
15
|
+
// MailConfig (org.ts, #162). null until an operator declares one -- the
|
|
16
|
+
// mailbox itself is blocked, not guessed, until it is.
|
|
17
|
+
ownerMailboxAddress: text('owner_mailbox_address'),
|
|
18
|
+
// Defaults to 'no-reply', the same literal `0019_mail_owner_mailbox.sql`
|
|
19
|
+
// and org.ts's `DEFAULT_NO_REPLY_LOCAL_PART` use.
|
|
20
|
+
noReplyLocalPart: text('no_reply_local_part').notNull().default('no-reply'),
|
|
21
|
+
// MailConfig.mailApps (org.ts, #34). Which ZITADEL apps in the org's
|
|
22
|
+
// audience project may read a person's mail; defaults to DEFAULT_MAIL_APPS
|
|
23
|
+
// ('agora', 'webmail') so every existing org keeps its current behaviour.
|
|
24
|
+
mailApps: jsonb('mail_apps').notNull().default(['agora', 'webmail']).$type(),
|
|
25
|
+
// MailConfig.roleMailboxes (org.ts, #230). Local parts of the role mailboxes
|
|
26
|
+
// pre-created on the org's mail domain; empty by default.
|
|
27
|
+
roleMailboxes: jsonb('role_mailboxes').notNull().default([]).$type(),
|
|
15
28
|
chatUsesDomain: boolean('chat_uses_domain').notNull().default(false),
|
|
16
29
|
callsEnabled: boolean('calls_enabled').notNull().default(false),
|
|
17
30
|
callsUsesDomain: boolean('calls_uses_domain').notNull().default(false),
|
package/dist/store.d.ts
CHANGED
|
@@ -14,6 +14,13 @@ import type { AppliedState } from './state.js';
|
|
|
14
14
|
export interface DeclaredOrgStore {
|
|
15
15
|
create(org: DeclaredOrg): Promise<void>;
|
|
16
16
|
get(id: OrgId): Promise<DeclaredOrg | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Every declared org, ordered by display name (#133). The read path a
|
|
19
|
+
* billing admin area (or anything else that just wants the roster) uses
|
|
20
|
+
* instead of reading the store's own table by hand -- Archon's
|
|
21
|
+
* `listCustomers` was doing exactly that before this existed.
|
|
22
|
+
*/
|
|
23
|
+
list(): Promise<DeclaredOrg[]>;
|
|
17
24
|
/** Whether any org already holds this slug. `slugFromName` asks it. */
|
|
18
25
|
slugTaken(slug: string): Promise<boolean>;
|
|
19
26
|
/**
|