@heychunky/core 0.13.0 → 0.15.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/admin/apps.d.ts +50 -0
- package/dist/admin/apps.d.ts.map +1 -1
- package/dist/admin/apps.js +69 -2
- package/dist/admin/apps.js.map +1 -1
- package/dist/admin/index.d.ts +2 -0
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +2 -0
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/migrations.d.ts +19 -0
- package/dist/admin/migrations.d.ts.map +1 -1
- package/dist/admin/migrations.js +27 -0
- package/dist/admin/migrations.js.map +1 -1
- package/dist/admin/orgs.d.ts +142 -0
- package/dist/admin/orgs.d.ts.map +1 -0
- package/dist/admin/orgs.js +166 -0
- package/dist/admin/orgs.js.map +1 -0
- package/dist/admin/provision.d.ts +10 -0
- package/dist/admin/provision.d.ts.map +1 -1
- package/dist/admin/provision.js +1 -1
- package/dist/admin/provision.js.map +1 -1
- package/dist/admin/register.d.ts +98 -0
- package/dist/admin/register.d.ts.map +1 -0
- package/dist/admin/register.js +191 -0
- package/dist/admin/register.js.map +1 -0
- package/dist/admin/run.d.ts.map +1 -1
- package/dist/admin/run.js +14 -1
- package/dist/admin/run.js.map +1 -1
- package/dist/admin/services/github.d.ts.map +1 -1
- package/dist/admin/services/github.js +15 -1
- package/dist/admin/services/github.js.map +1 -1
- package/dist/admin/services/vercel.d.ts +2 -0
- package/dist/admin/services/vercel.d.ts.map +1 -1
- package/dist/admin/services/vercel.js.map +1 -1
- package/dist/admin/users.d.ts +34 -0
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +45 -3
- package/dist/admin/users.js.map +1 -1
- package/dist/registry.d.ts +40 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +27 -0
- package/dist/registry.js.map +1 -1
- package/dist/services/db.d.ts +16 -0
- package/dist/services/db.d.ts.map +1 -1
- package/dist/services/db.js +27 -22
- package/dist/services/db.js.map +1 -1
- package/package.json +3 -2
- package/register/0001-baseline/01-invite-requests.sql +11 -0
- package/register/0001-baseline/02-invite-requests-email-key.sql +1 -0
- package/register/0001-baseline/03-login-codes.sql +12 -0
- package/register/0001-baseline/04-login-codes-email-idx.sql +1 -0
- package/register/0001-baseline/05-notifications.sql +11 -0
- package/register/0001-baseline/06-notifications-unread-idx.sql +1 -0
- package/register/0001-baseline/07-users.sql +13 -0
- package/register/0001-baseline/08-users-email-key.sql +1 -0
- package/register/0001-baseline/09-users-one-superadmin.sql +1 -0
- package/register/0001-baseline/10-users-username-unique.sql +1 -0
- package/register/0001-baseline/11-api-tokens.sql +13 -0
- package/register/0001-baseline/12-apps.sql +14 -0
- package/register/0001-baseline/13-apps-name-key.sql +1 -0
- package/register/0001-baseline/14-apps-owner-idx.sql +1 -0
- package/register/0001-baseline/15-contexts.sql +9 -0
- package/register/0001-baseline/16-credit-ledger.sql +10 -0
- package/register/0001-baseline/17-credit-ledger-user.sql +1 -0
- package/register/0001-baseline/18-runs.sql +16 -0
- package/register/0001-baseline/19-sessions.sql +10 -0
- package/register/0001-baseline/20-sessions-user-idx.sql +1 -0
- package/register/0001-baseline/21-app-chunks.sql +7 -0
- package/register/0001-baseline/22-context-members.sql +10 -0
- package/register/0002-orgs/01-orgs.sql +14 -0
- package/register/0002-orgs/02-orgs-name-key.sql +1 -0
- package/register/0002-orgs/03-org-members.sql +10 -0
- package/register/0002-orgs/04-org-members-user-idx.sql +1 -0
- package/register/0002-orgs/05-apps-org-id.sql +1 -0
- package/register/0002-orgs/06-apps-org-idx.sql +1 -0
- package/register/0002-orgs/07-apps-org-name-key.sql +1 -0
- package/register/0002-orgs/08-credit-ledger-org-id.sql +1 -0
- package/register/0002-orgs/09-credit-ledger-org-idx.sql +1 -0
- package/register/0002-orgs/10-api-tokens-org-id.sql +1 -0
- package/register/0002-orgs/11-api-tokens-created-by.sql +1 -0
- package/register/0002-orgs/12-backfill-personal-orgs.sql +15 -0
- package/register/0002-orgs/13-backfill-org-members.sql +12 -0
- package/register/0002-orgs/14-backfill-apps-org.sql +14 -0
- package/register/0002-orgs/15-backfill-credit-ledger-org.sql +11 -0
- package/register/0002-orgs/16-backfill-api-tokens-org.sql +12 -0
- package/register/README.md +73 -0
package/dist/admin/apps.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ import type { User } from "./users.ts";
|
|
|
19
19
|
export interface App {
|
|
20
20
|
id: string;
|
|
21
21
|
name: string;
|
|
22
|
+
/** The tenancy boundary. Null only for an owner who has claimed no username. */
|
|
23
|
+
org_id: string | null;
|
|
24
|
+
/** Who created it. Provenance, not tenancy — see orgs.ts. */
|
|
22
25
|
owner_id: string;
|
|
23
26
|
org: string | null;
|
|
24
27
|
repo: string | null;
|
|
@@ -70,6 +73,53 @@ export declare function assertCanCreate(owner: string, register: Register): Prom
|
|
|
70
73
|
* what a first, partial run could not, rather than making a second row.
|
|
71
74
|
*/
|
|
72
75
|
export declare function recordApp(app: AppRecord, register: Register): Promise<App>;
|
|
76
|
+
/**
|
|
77
|
+
* What a run found at the providers, in the shape the register stores.
|
|
78
|
+
*
|
|
79
|
+
* A step's detail is `name · extra` — `carmelcity/carmelcity.ai · private`,
|
|
80
|
+
* `carmelcity-ai`, `Carmel City Main` — so the name is everything before the
|
|
81
|
+
* separator. Three callers were each doing that split themselves; a fourth
|
|
82
|
+
* spelling it a fourth way is how the register ends up holding a repository
|
|
83
|
+
* called `carmelcity/carmelcity.ai ·`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function detailsFromRun(steps: readonly StepResult[]): Pick<AppRecord, "repo" | "vercelProject" | "neonProject">;
|
|
86
|
+
/** Refused because there is nothing of this app anywhere to adopt. */
|
|
87
|
+
export declare class NothingToAdoptError extends Error {
|
|
88
|
+
constructor(name: string);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Record an app the factory did not create.
|
|
92
|
+
*
|
|
93
|
+
* The register was only ever written at the end of a successful provision run,
|
|
94
|
+
* which is right for an app Chunky made and useless for one that predates it.
|
|
95
|
+
* An app can exist — repository, database, deployment, all live for months —
|
|
96
|
+
* and be invisible to every command that reads the register.
|
|
97
|
+
*
|
|
98
|
+
* This is deliberately not `--adopt`, which means *create the missing pieces
|
|
99
|
+
* on a live app*. This creates nothing, ever: it takes a walk that already
|
|
100
|
+
* happened in plan mode and writes down what that walk found.
|
|
101
|
+
*
|
|
102
|
+
* Refusing when nothing exists is the guard that matters. Recording an app
|
|
103
|
+
* that is not there would put a row in the register pointing at no repository
|
|
104
|
+
* and no database, and every later command would believe it.
|
|
105
|
+
*/
|
|
106
|
+
export declare function adoptApp(steps: readonly StepResult[], app: {
|
|
107
|
+
name: string;
|
|
108
|
+
owner: string;
|
|
109
|
+
org?: string | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* A Neon project the walk cannot find by name.
|
|
112
|
+
*
|
|
113
|
+
* `matchByName` generates the forms a provider plausibly used, and a
|
|
114
|
+
* project somebody named by hand years ago is often none of them —
|
|
115
|
+
* `carmelcity.ai` never reaches `Carmel City Main`. No lookup fixes that,
|
|
116
|
+
* because the two strings share no rule. So it is stated rather than
|
|
117
|
+
* guessed, and the caller is expected to have checked it is real: a
|
|
118
|
+
* register pointing at a database that does not exist is worse than one
|
|
119
|
+
* admitting it does not know.
|
|
120
|
+
*/
|
|
121
|
+
neonProject?: string | undefined;
|
|
122
|
+
}, register: Register): Promise<App>;
|
|
73
123
|
/** Apps an owner has, or every app when none is named. */
|
|
74
124
|
/**
|
|
75
125
|
* One app, if this operator may have it.
|
package/dist/admin/apps.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;CAClC;AAED,sEAAsE;AACtE,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAKzC;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,SAAS,CAAC;CACxC;AAED,2EAA2E;AAC3E,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5F;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBtF;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"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"}
|
package/dist/admin/apps.js
CHANGED
|
@@ -66,9 +66,23 @@ export async function assertCanCreate(owner, register) {
|
|
|
66
66
|
*/
|
|
67
67
|
export async function recordApp(app, register) {
|
|
68
68
|
const conn = register;
|
|
69
|
-
const { rows } = await conn.write(
|
|
70
|
-
|
|
69
|
+
const { rows } = await conn.write(
|
|
70
|
+
// `org_id` is derived rather than passed, and derived the same way the
|
|
71
|
+
// backfill derived it: through the owner's username to their personal
|
|
72
|
+
// organisation. A caller that had to supply it is a caller that can supply
|
|
73
|
+
// the wrong one, and the invariant `heychunky register verify` asserts —
|
|
74
|
+
// every app is in an organisation — would then hold only until the next
|
|
75
|
+
// app was made.
|
|
76
|
+
//
|
|
77
|
+
// Kept on conflict, never overwritten. Re-running provisioning fills in
|
|
78
|
+
// what a partial run could not; it must not move an app between tenants.
|
|
79
|
+
`insert into apps (name, owner_id, org_id, org, repo, vercel_project, neon_project, provisioned)
|
|
80
|
+
values ($1, $2,
|
|
81
|
+
(select o.id from orgs o join users u on lower(o.name) = lower(u.username)
|
|
82
|
+
where u.id = $2 and o.kind = 'personal'),
|
|
83
|
+
$3, $4, $5, $6, $7)
|
|
71
84
|
on conflict (lower(name)) do update set
|
|
85
|
+
org_id = coalesce(apps.org_id, excluded.org_id),
|
|
72
86
|
org = coalesce(excluded.org, apps.org),
|
|
73
87
|
repo = coalesce(excluded.repo, apps.repo),
|
|
74
88
|
vercel_project = coalesce(excluded.vercel_project, apps.vercel_project),
|
|
@@ -79,6 +93,59 @@ export async function recordApp(app, register) {
|
|
|
79
93
|
app.provisioned ? JSON.stringify(app.provisioned) : null]);
|
|
80
94
|
return rows[0];
|
|
81
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* What a run found at the providers, in the shape the register stores.
|
|
98
|
+
*
|
|
99
|
+
* A step's detail is `name · extra` — `carmelcity/carmelcity.ai · private`,
|
|
100
|
+
* `carmelcity-ai`, `Carmel City Main` — so the name is everything before the
|
|
101
|
+
* separator. Three callers were each doing that split themselves; a fourth
|
|
102
|
+
* spelling it a fourth way is how the register ends up holding a repository
|
|
103
|
+
* called `carmelcity/carmelcity.ai ·`.
|
|
104
|
+
*/
|
|
105
|
+
export function detailsFromRun(steps) {
|
|
106
|
+
const detail = (step) => steps.find((s) => s.step === step && (s.state === "exists" || s.state === "created"))
|
|
107
|
+
?.detail?.split(" · ")[0];
|
|
108
|
+
return {
|
|
109
|
+
repo: detail("github repo"),
|
|
110
|
+
vercelProject: detail("vercel project"),
|
|
111
|
+
neonProject: detail("neon project"),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Refused because there is nothing of this app anywhere to adopt. */
|
|
115
|
+
export class NothingToAdoptError extends Error {
|
|
116
|
+
constructor(name) {
|
|
117
|
+
super(`Nothing of ${name} exists at any provider. Use \`heychunky new\` to make it.`);
|
|
118
|
+
this.name = "NothingToAdoptError";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Record an app the factory did not create.
|
|
123
|
+
*
|
|
124
|
+
* The register was only ever written at the end of a successful provision run,
|
|
125
|
+
* which is right for an app Chunky made and useless for one that predates it.
|
|
126
|
+
* An app can exist — repository, database, deployment, all live for months —
|
|
127
|
+
* and be invisible to every command that reads the register.
|
|
128
|
+
*
|
|
129
|
+
* This is deliberately not `--adopt`, which means *create the missing pieces
|
|
130
|
+
* on a live app*. This creates nothing, ever: it takes a walk that already
|
|
131
|
+
* happened in plan mode and writes down what that walk found.
|
|
132
|
+
*
|
|
133
|
+
* Refusing when nothing exists is the guard that matters. Recording an app
|
|
134
|
+
* that is not there would put a row in the register pointing at no repository
|
|
135
|
+
* and no database, and every later command would believe it.
|
|
136
|
+
*/
|
|
137
|
+
export async function adoptApp(steps, app, register) {
|
|
138
|
+
if (!steps.some((s) => s.state === "exists"))
|
|
139
|
+
throw new NothingToAdoptError(app.name);
|
|
140
|
+
const found = detailsFromRun(steps);
|
|
141
|
+
return recordApp({
|
|
142
|
+
name: app.name,
|
|
143
|
+
owner: app.owner,
|
|
144
|
+
org: app.org,
|
|
145
|
+
...found,
|
|
146
|
+
neonProject: app.neonProject ?? found.neonProject,
|
|
147
|
+
}, register);
|
|
148
|
+
}
|
|
82
149
|
/** Apps an owner has, or every app when none is named. */
|
|
83
150
|
/**
|
|
84
151
|
* One app, if this operator may have it.
|
package/dist/admin/apps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"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"}
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -8,11 +8,13 @@ export * from "./secrets/infisical.ts";
|
|
|
8
8
|
export * from "./syncenv.ts";
|
|
9
9
|
export * from "./master.ts";
|
|
10
10
|
export * from "./apps.ts";
|
|
11
|
+
export * from "./orgs.ts";
|
|
11
12
|
export * from "./users.ts";
|
|
12
13
|
export * from "./run.ts";
|
|
13
14
|
export * from "./provision.ts";
|
|
14
15
|
export * from "./decommission.ts";
|
|
15
16
|
export * from "./migrations.ts";
|
|
17
|
+
export * from "./register.ts";
|
|
16
18
|
export * from "./promote.ts";
|
|
17
19
|
export * from "./runs.ts";
|
|
18
20
|
export * from "./entrypoints.ts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAG5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAG5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
package/dist/admin/index.js
CHANGED
|
@@ -8,11 +8,13 @@ export * from "./secrets/infisical.js";
|
|
|
8
8
|
export * from "./syncenv.js";
|
|
9
9
|
export * from "./master.js";
|
|
10
10
|
export * from "./apps.js";
|
|
11
|
+
export * from "./orgs.js";
|
|
11
12
|
export * from "./users.js";
|
|
12
13
|
export * from "./run.js";
|
|
13
14
|
export * from "./provision.js";
|
|
14
15
|
export * from "./decommission.js";
|
|
15
16
|
export * from "./migrations.js";
|
|
17
|
+
export * from "./register.js";
|
|
16
18
|
export * from "./promote.js";
|
|
17
19
|
export * from "./runs.js";
|
|
18
20
|
export * from "./entrypoints.js";
|
package/dist/admin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAE5B,+CAA+C;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAE5B,+CAA+C;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
|
@@ -95,4 +95,23 @@ export declare function pending(all: Migration[], have: Set<string>): Migration[
|
|
|
95
95
|
* failed, and running them anyway turns one clear error into several.
|
|
96
96
|
*/
|
|
97
97
|
export declare function apply(db: Ledger, migrations: Migration[], watch?: (m: Migration) => void): Promise<number>;
|
|
98
|
+
/**
|
|
99
|
+
* Record statements as applied without running them.
|
|
100
|
+
*
|
|
101
|
+
* For a database that predates its ledger. The register is the case this was
|
|
102
|
+
* written for — `apps` and `users` were typed into psql long before anything
|
|
103
|
+
* wrote them down, so the first migration file describing them is a record of
|
|
104
|
+
* what is already there rather than work to do. Running it would fail on every
|
|
105
|
+
* `create table`, and failing is not the answer: the database is *correct*, it
|
|
106
|
+
* simply has no receipt.
|
|
107
|
+
*
|
|
108
|
+
* Deliberately separate from `apply`, and deliberately not a flag on it. One
|
|
109
|
+
* function that either runs SQL or does not, depending on an argument, is one
|
|
110
|
+
* a caller can get backwards — and getting this one backwards means a schema
|
|
111
|
+
* change silently skipped, discovered later as a missing column in production.
|
|
112
|
+
*
|
|
113
|
+
* Only ever right when somebody has looked at the database and confirmed the
|
|
114
|
+
* statements are already true of it. There is no way to check that from here.
|
|
115
|
+
*/
|
|
116
|
+
export declare function adopt(db: Ledger, migrations: Migration[]): Promise<string[]>;
|
|
98
117
|
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,kEAAkE;AAClE,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C,oEAAoE;AACpE,eAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8DAA8D;AAC9D,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIlD;AAED,oEAAoE;AACpE,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK5D;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAS9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,CAExE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,KAAK,CACzB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,SAAS,EAAE,EACvB,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,GAC7B,OAAO,CAAC,MAAM,CAAC,CAUjB"}
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,kEAAkE;AAClE,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C,oEAAoE;AACpE,eAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8DAA8D;AAC9D,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIlD;AAED,oEAAoE;AACpE,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK5D;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAS9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,CAExE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,KAAK,CACzB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,SAAS,EAAE,EACvB,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,GAC7B,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQlF"}
|
package/dist/admin/migrations.js
CHANGED
|
@@ -90,4 +90,31 @@ export async function apply(db, migrations, watch) {
|
|
|
90
90
|
}
|
|
91
91
|
return ran;
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Record statements as applied without running them.
|
|
95
|
+
*
|
|
96
|
+
* For a database that predates its ledger. The register is the case this was
|
|
97
|
+
* written for — `apps` and `users` were typed into psql long before anything
|
|
98
|
+
* wrote them down, so the first migration file describing them is a record of
|
|
99
|
+
* what is already there rather than work to do. Running it would fail on every
|
|
100
|
+
* `create table`, and failing is not the answer: the database is *correct*, it
|
|
101
|
+
* simply has no receipt.
|
|
102
|
+
*
|
|
103
|
+
* Deliberately separate from `apply`, and deliberately not a flag on it. One
|
|
104
|
+
* function that either runs SQL or does not, depending on an argument, is one
|
|
105
|
+
* a caller can get backwards — and getting this one backwards means a schema
|
|
106
|
+
* change silently skipped, discovered later as a missing column in production.
|
|
107
|
+
*
|
|
108
|
+
* Only ever right when somebody has looked at the database and confirmed the
|
|
109
|
+
* statements are already true of it. There is no way to check that from here.
|
|
110
|
+
*/
|
|
111
|
+
export async function adopt(db, migrations) {
|
|
112
|
+
await ensureLedger(db);
|
|
113
|
+
const recorded = [];
|
|
114
|
+
for (const m of migrations) {
|
|
115
|
+
await db.write(`insert into ${LEDGER} (id) values ($1) on conflict (id) do nothing`, [m.id]);
|
|
116
|
+
recorded.push(m.id);
|
|
117
|
+
}
|
|
118
|
+
return recorded;
|
|
119
|
+
}
|
|
93
120
|
//# sourceMappingURL=migrations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tenancy boundary.
|
|
3
|
+
*
|
|
4
|
+
* An **organisation** is what owns things. A **user** is a person who builds.
|
|
5
|
+
* Not the users of anybody's app — Carmel's citizens are a Carmel City concept
|
|
6
|
+
* and stay in Carmel's database; these are the people who make apps here.
|
|
7
|
+
*
|
|
8
|
+
* `owner_id` on `apps` was the boundary until now, and a person is the wrong
|
|
9
|
+
* shape for one: it cannot be shared, it cannot be handed over, and it cannot
|
|
10
|
+
* hold a plan two people are both paying against. `org_id` is the boundary and
|
|
11
|
+
* `owner_id` survives beside it as provenance — who created this.
|
|
12
|
+
*
|
|
13
|
+
* **An organisation's name is the publish scope.** The curated shelf keeps bare
|
|
14
|
+
* names because somebody chose them; everyone else publishes under a scope, and
|
|
15
|
+
* `@carmelcity/auth` needs no second entity to explain what `carmelcity` is. It
|
|
16
|
+
* is also the URL segment a signed-in session is scoped by, which is why the
|
|
17
|
+
* database insists it is a slug rather than a title: one string, three jobs,
|
|
18
|
+
* and nothing anywhere has to remember which spelling it is holding.
|
|
19
|
+
*
|
|
20
|
+
* **The organisation is not the GitHub organisation — it has one.** Carmel's
|
|
21
|
+
* organisation is `carmelcity` here and `carmelcity` on GitHub today, and
|
|
22
|
+
* nothing should be built on those staying equal; `github_org`, `vercel_team`
|
|
23
|
+
* and `vault_folder` are where a provider's own name for the same tenant goes.
|
|
24
|
+
*
|
|
25
|
+
* **One level.** No spaces or workspaces inside an organisation until something
|
|
26
|
+
* needs one.
|
|
27
|
+
*
|
|
28
|
+
* Two kinds of role, and they live in different places on purpose. `superadmin`
|
|
29
|
+
* is a factory role and stays on the user, where a partial unique index permits
|
|
30
|
+
* exactly one of them. `admin` and `publisher` are organisation roles and live
|
|
31
|
+
* on the membership. One column for both would let an organisation's own admin
|
|
32
|
+
* inherit the factory.
|
|
33
|
+
*/
|
|
34
|
+
import type { Register } from "./master.ts";
|
|
35
|
+
import type { PlanKey } from "./users.ts";
|
|
36
|
+
/** What somebody may do inside one organisation. Never a factory role. */
|
|
37
|
+
export declare const ORG_ROLES: readonly ["admin", "publisher"];
|
|
38
|
+
export type OrgRole = (typeof ORG_ROLES)[number];
|
|
39
|
+
/**
|
|
40
|
+
* Whether an organisation is a person or a group.
|
|
41
|
+
*
|
|
42
|
+
* `personal` is made for you when you claim a username and is never made any
|
|
43
|
+
* other way, which is what keeps a name meaning one thing across the People
|
|
44
|
+
* page, the shelf and the URL.
|
|
45
|
+
*/
|
|
46
|
+
export declare const ORG_KINDS: readonly ["personal", "team"];
|
|
47
|
+
export type OrgKind = (typeof ORG_KINDS)[number];
|
|
48
|
+
/**
|
|
49
|
+
* The shape a name may take.
|
|
50
|
+
*
|
|
51
|
+
* The same rule as a chunk's scope and a username, because they are the same
|
|
52
|
+
* string. Enforced by a check constraint as well as here — a rule that lives
|
|
53
|
+
* only in TypeScript is a rule the next caller writes around.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ORG_NAME: RegExp;
|
|
56
|
+
export interface Org {
|
|
57
|
+
id: string;
|
|
58
|
+
/** The publish scope and the URL segment. Unique without case. */
|
|
59
|
+
name: string;
|
|
60
|
+
kind: OrgKind;
|
|
61
|
+
/** What this tenant may spend. The plan moved here from the user. */
|
|
62
|
+
plan: PlanKey;
|
|
63
|
+
/** A provider's own name for the same tenant. Null until one is linked. */
|
|
64
|
+
github_org: string | null;
|
|
65
|
+
vercel_team: string | null;
|
|
66
|
+
vault_folder: string | null;
|
|
67
|
+
created_at: string;
|
|
68
|
+
}
|
|
69
|
+
/** An organisation with what the person asking may do in it. */
|
|
70
|
+
export interface Membership extends Org {
|
|
71
|
+
role: OrgRole;
|
|
72
|
+
}
|
|
73
|
+
/** Refused because the name belongs to an organisation that is not personal. */
|
|
74
|
+
export declare class OrgNameTakenError extends Error {
|
|
75
|
+
constructor(name: string);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Give a user the organisation that matches their username.
|
|
79
|
+
*
|
|
80
|
+
* Runs in the same moment as claiming the name, because a name that is yours on
|
|
81
|
+
* the People page and somebody else's on the shelf would be worse than having
|
|
82
|
+
* no names at all. Safe to run again: if the membership insert fails the name is
|
|
83
|
+
* still theirs and the organisation is still there, which is the right way round
|
|
84
|
+
* — a person without their scope yet is inconvenienced, a person whose scope
|
|
85
|
+
* belongs to somebody else is a bug you cannot undo.
|
|
86
|
+
*
|
|
87
|
+
* Three statements rather than one because the client sends one statement per
|
|
88
|
+
* request. The insert is the check for the same reason `claimUsername`'s is: two
|
|
89
|
+
* people reaching for a name in the same second is a race only the unique index
|
|
90
|
+
* can settle.
|
|
91
|
+
*
|
|
92
|
+
* The read between the two writes is not ceremony. `on conflict do nothing` is
|
|
93
|
+
* silent about *why* it did nothing, and the one case that matters is a team
|
|
94
|
+
* organisation already holding this name — adding the caller to that as an admin
|
|
95
|
+
* would hand them somebody else's tenant. So it is looked at, and refused.
|
|
96
|
+
*/
|
|
97
|
+
export declare function claimPersonalOrg(userId: string, username: string, register: Register): Promise<Org>;
|
|
98
|
+
/** The organisations this person belongs to, personal one first. */
|
|
99
|
+
export declare function orgsFor(userId: string, register: Register): Promise<Membership[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Whether this person may publish under this name.
|
|
102
|
+
*
|
|
103
|
+
* Membership is the whole answer — both roles publish, which is what makes
|
|
104
|
+
* `publisher` worth having as the one that cannot also invite people.
|
|
105
|
+
*/
|
|
106
|
+
export declare function mayPublish(userId: string, org: string, register: Register): Promise<boolean>;
|
|
107
|
+
/**
|
|
108
|
+
* What must be true of the register once organisations are backfilled.
|
|
109
|
+
*
|
|
110
|
+
* Written as data, and as violations rather than assertions, for one reason:
|
|
111
|
+
* **a wrong backfill does not fail loudly.** It sits looking fine until the day
|
|
112
|
+
* reads move onto `org_id`, and then it is one customer seeing another's apps.
|
|
113
|
+
* Nothing about that shows up in a test — the schema is right either way — so
|
|
114
|
+
* the proof has to be a question asked of the live rows.
|
|
115
|
+
*
|
|
116
|
+
* Every check counts rows that should not exist. Zero is the only passing
|
|
117
|
+
* answer, which means a check that is wrong tends to fail rather than pass, and
|
|
118
|
+
* a table that is empty passes honestly rather than vacuously hiding a bug.
|
|
119
|
+
*
|
|
120
|
+
* Exported so the CLI can print the SQL beside the count. A verification
|
|
121
|
+
* somebody has to take on trust is not one.
|
|
122
|
+
*/
|
|
123
|
+
export declare const ORG_CHECKS: readonly {
|
|
124
|
+
check: string;
|
|
125
|
+
sql: string;
|
|
126
|
+
}[];
|
|
127
|
+
export interface CheckResult {
|
|
128
|
+
check: string;
|
|
129
|
+
sql: string;
|
|
130
|
+
/** Rows that should not exist. Anything but zero is a finding. */
|
|
131
|
+
found: number;
|
|
132
|
+
ok: boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Ask the register all of them.
|
|
136
|
+
*
|
|
137
|
+
* Read-only, and safe to run before the migration as well as after — a check
|
|
138
|
+
* against a table that does not exist yet is a failure worth seeing rather than
|
|
139
|
+
* one to swallow, so nothing here catches.
|
|
140
|
+
*/
|
|
141
|
+
export declare function verifyOrgs(register: Register): Promise<CheckResult[]>;
|
|
142
|
+
//# sourceMappingURL=orgs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../src/admin/orgs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,0EAA0E;AAC1E,eAAO,MAAM,SAAS,iCAAkC,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,+BAAgC,CAAC;AACvD,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,QAAyB,CAAC;AAE/C,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,qEAAqE;IACrE,IAAI,EAAE,OAAO,CAAC;IACd,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,UAAW,SAAQ,GAAG;IACrC,IAAI,EAAE,OAAO,CAAC;CACf;AAED,gFAAgF;AAChF,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,IAAI,EAAE,MAAM;CAIzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,GAAG,CAAC,CAwBd;AAED,oEAAoE;AACpE,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvF;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAqD/D,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAQ3E"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** What somebody may do inside one organisation. Never a factory role. */
|
|
2
|
+
export const ORG_ROLES = ["admin", "publisher"];
|
|
3
|
+
/**
|
|
4
|
+
* Whether an organisation is a person or a group.
|
|
5
|
+
*
|
|
6
|
+
* `personal` is made for you when you claim a username and is never made any
|
|
7
|
+
* other way, which is what keeps a name meaning one thing across the People
|
|
8
|
+
* page, the shelf and the URL.
|
|
9
|
+
*/
|
|
10
|
+
export const ORG_KINDS = ["personal", "team"];
|
|
11
|
+
/**
|
|
12
|
+
* The shape a name may take.
|
|
13
|
+
*
|
|
14
|
+
* The same rule as a chunk's scope and a username, because they are the same
|
|
15
|
+
* string. Enforced by a check constraint as well as here — a rule that lives
|
|
16
|
+
* only in TypeScript is a rule the next caller writes around.
|
|
17
|
+
*/
|
|
18
|
+
export const ORG_NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
19
|
+
/** Refused because the name belongs to an organisation that is not personal. */
|
|
20
|
+
export class OrgNameTakenError extends Error {
|
|
21
|
+
constructor(name) {
|
|
22
|
+
super(`"${name}" is a team organisation, so it cannot also be somebody's personal one.`);
|
|
23
|
+
this.name = "OrgNameTakenError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Give a user the organisation that matches their username.
|
|
28
|
+
*
|
|
29
|
+
* Runs in the same moment as claiming the name, because a name that is yours on
|
|
30
|
+
* the People page and somebody else's on the shelf would be worse than having
|
|
31
|
+
* no names at all. Safe to run again: if the membership insert fails the name is
|
|
32
|
+
* still theirs and the organisation is still there, which is the right way round
|
|
33
|
+
* — a person without their scope yet is inconvenienced, a person whose scope
|
|
34
|
+
* belongs to somebody else is a bug you cannot undo.
|
|
35
|
+
*
|
|
36
|
+
* Three statements rather than one because the client sends one statement per
|
|
37
|
+
* request. The insert is the check for the same reason `claimUsername`'s is: two
|
|
38
|
+
* people reaching for a name in the same second is a race only the unique index
|
|
39
|
+
* can settle.
|
|
40
|
+
*
|
|
41
|
+
* The read between the two writes is not ceremony. `on conflict do nothing` is
|
|
42
|
+
* silent about *why* it did nothing, and the one case that matters is a team
|
|
43
|
+
* organisation already holding this name — adding the caller to that as an admin
|
|
44
|
+
* would hand them somebody else's tenant. So it is looked at, and refused.
|
|
45
|
+
*/
|
|
46
|
+
export async function claimPersonalOrg(userId, username, register) {
|
|
47
|
+
if (!ORG_NAME.test(username))
|
|
48
|
+
throw new Error(`"${username}" is not a name an organisation can have.`);
|
|
49
|
+
await register.write(`insert into orgs (name, kind, plan)
|
|
50
|
+
select $1, 'personal', coalesce(u.plan, 'free') from users u where u.id = $2
|
|
51
|
+
on conflict (lower(name)) do nothing`, [username, userId]);
|
|
52
|
+
const { rows } = await register.query("select * from orgs where lower(name) = lower($1)", [username]);
|
|
53
|
+
const org = rows[0];
|
|
54
|
+
if (!org)
|
|
55
|
+
throw new Error(`No user ${userId}, so "${username}" belongs to nobody.`);
|
|
56
|
+
if (org.kind !== "personal")
|
|
57
|
+
throw new OrgNameTakenError(username);
|
|
58
|
+
await register.write(`insert into org_members (org_id, user_id, role) values ($1, $2, 'admin')
|
|
59
|
+
on conflict (org_id, user_id) do nothing`, [org.id, userId]);
|
|
60
|
+
return org;
|
|
61
|
+
}
|
|
62
|
+
/** The organisations this person belongs to, personal one first. */
|
|
63
|
+
export async function orgsFor(userId, register) {
|
|
64
|
+
const { rows } = await register.query(`select o.*, m.role from orgs o join org_members m on m.org_id = o.id
|
|
65
|
+
where m.user_id = $1
|
|
66
|
+
order by (o.kind = 'personal') desc, o.name`, [userId]);
|
|
67
|
+
return rows;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether this person may publish under this name.
|
|
71
|
+
*
|
|
72
|
+
* Membership is the whole answer — both roles publish, which is what makes
|
|
73
|
+
* `publisher` worth having as the one that cannot also invite people.
|
|
74
|
+
*/
|
|
75
|
+
export async function mayPublish(userId, org, register) {
|
|
76
|
+
const { rows } = await register.query(`select 1 from org_members m join orgs o on o.id = m.org_id
|
|
77
|
+
where m.user_id = $1 and lower(o.name) = lower($2)`, [userId, org]);
|
|
78
|
+
return rows.length > 0;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What must be true of the register once organisations are backfilled.
|
|
82
|
+
*
|
|
83
|
+
* Written as data, and as violations rather than assertions, for one reason:
|
|
84
|
+
* **a wrong backfill does not fail loudly.** It sits looking fine until the day
|
|
85
|
+
* reads move onto `org_id`, and then it is one customer seeing another's apps.
|
|
86
|
+
* Nothing about that shows up in a test — the schema is right either way — so
|
|
87
|
+
* the proof has to be a question asked of the live rows.
|
|
88
|
+
*
|
|
89
|
+
* Every check counts rows that should not exist. Zero is the only passing
|
|
90
|
+
* answer, which means a check that is wrong tends to fail rather than pass, and
|
|
91
|
+
* a table that is empty passes honestly rather than vacuously hiding a bug.
|
|
92
|
+
*
|
|
93
|
+
* Exported so the CLI can print the SQL beside the count. A verification
|
|
94
|
+
* somebody has to take on trust is not one.
|
|
95
|
+
*/
|
|
96
|
+
export const ORG_CHECKS = [
|
|
97
|
+
{
|
|
98
|
+
check: "users with a username and no personal organisation",
|
|
99
|
+
sql: `select count(*) as n from users u where u.username is not null
|
|
100
|
+
and not exists (select 1 from orgs o
|
|
101
|
+
where lower(o.name) = lower(u.username) and o.kind = 'personal')`,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
check: "personal organisations whose name is nobody's username",
|
|
105
|
+
sql: `select count(*) as n from orgs o where o.kind = 'personal'
|
|
106
|
+
and not exists (select 1 from users u where lower(u.username) = lower(o.name))`,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
check: "personal organisations without exactly one admin member",
|
|
110
|
+
sql: `select count(*) as n from orgs o where o.kind = 'personal'
|
|
111
|
+
and (select count(*) from org_members m
|
|
112
|
+
where m.org_id = o.id and m.role = 'admin') <> 1`,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
check: "members of an organisation they are not in the register of",
|
|
116
|
+
sql: `select count(*) as n from org_members m
|
|
117
|
+
where not exists (select 1 from users u where u.id = m.user_id)`,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
check: "apps in no organisation",
|
|
121
|
+
sql: "select count(*) as n from apps where org_id is null",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
check: "apps whose organisation is not their owner's personal one",
|
|
125
|
+
sql: `select count(*) as n from apps a
|
|
126
|
+
join users u on u.id = a.owner_id
|
|
127
|
+
where a.org_id is not null
|
|
128
|
+
and a.org_id is distinct from
|
|
129
|
+
(select o.id from orgs o
|
|
130
|
+
where lower(o.name) = lower(u.username) and o.kind = 'personal')`,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
check: "apps whose owner has claimed no username, so can have no organisation",
|
|
134
|
+
sql: `select count(*) as n from apps a join users u on u.id = a.owner_id
|
|
135
|
+
where u.username is null`,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
check: "credit entries in no organisation",
|
|
139
|
+
sql: "select count(*) as n from credit_ledger where org_id is null",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
check: "api tokens in no organisation",
|
|
143
|
+
sql: "select count(*) as n from api_tokens where org_id is null",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
check: "api tokens with no recorded creator",
|
|
147
|
+
sql: "select count(*) as n from api_tokens where created_by is null",
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
/**
|
|
151
|
+
* Ask the register all of them.
|
|
152
|
+
*
|
|
153
|
+
* Read-only, and safe to run before the migration as well as after — a check
|
|
154
|
+
* against a table that does not exist yet is a failure worth seeing rather than
|
|
155
|
+
* one to swallow, so nothing here catches.
|
|
156
|
+
*/
|
|
157
|
+
export async function verifyOrgs(register) {
|
|
158
|
+
const out = [];
|
|
159
|
+
for (const { check, sql } of ORG_CHECKS) {
|
|
160
|
+
const { rows } = await register.query(sql);
|
|
161
|
+
const found = Number(rows[0]?.["n"] ?? 0);
|
|
162
|
+
out.push({ check, sql, found, ok: found === 0 });
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=orgs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgs.js","sourceRoot":"","sources":["../../src/admin/orgs.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,6 +18,16 @@ export interface AppSpec {
|
|
|
18
18
|
* a domain of your own is a decision taken later, on top of one that works.
|
|
19
19
|
*/
|
|
20
20
|
domain?: boolean | string;
|
|
21
|
+
/**
|
|
22
|
+
* The Neon project, when it is not named after the app.
|
|
23
|
+
*
|
|
24
|
+
* `findProject` matches exactly, and a project somebody named by hand years
|
|
25
|
+
* ago rarely matches the app: `carmelcity.ai` never reaches `Carmel City
|
|
26
|
+
* Main`. No rule connects those two strings, so for an app the register
|
|
27
|
+
* already knows, the register says which project it is rather than the walk
|
|
28
|
+
* guessing and reporting a live database as missing.
|
|
29
|
+
*/
|
|
30
|
+
neonProject?: string;
|
|
21
31
|
/**
|
|
22
32
|
* What kind of app it is, which decides what the repository starts as.
|
|
23
33
|
*
|