@indecisivedev/openplan 0.2.0-alpha.2
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 +120 -0
- package/dist/access-policy.d.ts +35 -0
- package/dist/access-policy.js +107 -0
- package/dist/access-policy.js.map +1 -0
- package/dist/access-store.d.ts +33 -0
- package/dist/access-store.js +212 -0
- package/dist/access-store.js.map +1 -0
- package/dist/admin.d.ts +1 -0
- package/dist/admin.js +13 -0
- package/dist/admin.js.map +1 -0
- package/dist/api.d.ts +2 -0
- package/dist/api.js +867 -0
- package/dist/api.js.map +1 -0
- package/dist/bootstrap-admin.d.ts +1 -0
- package/dist/bootstrap-admin.js +35 -0
- package/dist/bootstrap-admin.js.map +1 -0
- package/dist/browser-security.d.ts +5 -0
- package/dist/browser-security.js +53 -0
- package/dist/browser-security.js.map +1 -0
- package/dist/celld-quickstart.d.ts +51 -0
- package/dist/celld-quickstart.js +1175 -0
- package/dist/celld-quickstart.js.map +1 -0
- package/dist/cli-api-client.d.ts +78 -0
- package/dist/cli-api-client.js +272 -0
- package/dist/cli-api-client.js.map +1 -0
- package/dist/cli-credentials.d.ts +47 -0
- package/dist/cli-credentials.js +447 -0
- package/dist/cli-credentials.js.map +1 -0
- package/dist/cli-endpoint.d.ts +19 -0
- package/dist/cli-endpoint.js +56 -0
- package/dist/cli-endpoint.js.map +1 -0
- package/dist/cli-program.d.ts +11 -0
- package/dist/cli-program.js +934 -0
- package/dist/cli-program.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-ip.d.ts +4 -0
- package/dist/client-ip.js +49 -0
- package/dist/client-ip.js.map +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +113 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +3 -0
- package/dist/crypto.js +11 -0
- package/dist/crypto.js.map +1 -0
- package/dist/database.d.ts +15 -0
- package/dist/database.js +155 -0
- package/dist/database.js.map +1 -0
- package/dist/db.d.ts +31 -0
- package/dist/db.js +388 -0
- package/dist/db.js.map +1 -0
- package/dist/drafts.d.ts +43 -0
- package/dist/drafts.js +90 -0
- package/dist/drafts.js.map +1 -0
- package/dist/feature-settings.d.ts +7 -0
- package/dist/feature-settings.js +32 -0
- package/dist/feature-settings.js.map +1 -0
- package/dist/generated/annotation-client.d.ts +1 -0
- package/dist/generated/annotation-client.js +2 -0
- package/dist/generated/annotation-client.js.map +1 -0
- package/dist/html-policy.d.ts +15 -0
- package/dist/html-policy.js +172 -0
- package/dist/html-policy.js.map +1 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +10 -0
- package/dist/ids.js.map +1 -0
- package/dist/local-auth.d.ts +36 -0
- package/dist/local-auth.js +215 -0
- package/dist/local-auth.js.map +1 -0
- package/dist/maintenance.d.ts +1 -0
- package/dist/maintenance.js +18 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/oidc.d.ts +39 -0
- package/dist/oidc.js +194 -0
- package/dist/oidc.js.map +1 -0
- package/dist/public-url.d.ts +17 -0
- package/dist/public-url.js +96 -0
- package/dist/public-url.js.map +1 -0
- package/dist/rate-limit.d.ts +25 -0
- package/dist/rate-limit.js +55 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/reconcile.d.ts +5 -0
- package/dist/reconcile.js +18 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/render-annotations.d.ts +10 -0
- package/dist/render-annotations.js +73 -0
- package/dist/render-annotations.js.map +1 -0
- package/dist/render-web.d.ts +74 -0
- package/dist/render-web.js +439 -0
- package/dist/render-web.js.map +1 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +85 -0
- package/dist/render.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +26 -0
- package/dist/server.js.map +1 -0
- package/dist/shoo.d.ts +26 -0
- package/dist/shoo.js +92 -0
- package/dist/shoo.js.map +1 -0
- package/dist/sqlite-migrations.d.ts +6 -0
- package/dist/sqlite-migrations.js +214 -0
- package/dist/sqlite-migrations.js.map +1 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.js +86 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upload-state.d.ts +19 -0
- package/dist/upload-state.js +118 -0
- package/dist/upload-state.js.map +1 -0
- package/dist/web-auth.d.ts +33 -0
- package/dist/web-auth.js +122 -0
- package/dist/web-auth.js.map +1 -0
- package/dist/web.d.ts +2 -0
- package/dist/web.js +550 -0
- package/dist/web.js.map +1 -0
- package/package.json +70 -0
package/dist/db.js
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { config } from "./config.js";
|
|
2
|
+
import { sha256 } from "./crypto.js";
|
|
3
|
+
import { database } from "./database.js";
|
|
4
|
+
import { newInternalId } from "./ids.js";
|
|
5
|
+
export const publicUploadAuth = {
|
|
6
|
+
keyHash: "",
|
|
7
|
+
id: "key_public_upload",
|
|
8
|
+
account_id: "acct_public_upload",
|
|
9
|
+
name: "Public Uploads",
|
|
10
|
+
account_name: "Public Uploads"
|
|
11
|
+
};
|
|
12
|
+
export const pool = database;
|
|
13
|
+
export async function initDb() {
|
|
14
|
+
await database.init();
|
|
15
|
+
if (database.kind === "sqlite") {
|
|
16
|
+
await ensurePublicUploadApiKey();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await pool.query(`
|
|
20
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
name TEXT NOT NULL,
|
|
23
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
24
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS draft_access_policies (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
mode TEXT NOT NULL CHECK (mode IN ('private', 'password', 'public')),
|
|
30
|
+
password_algorithm TEXT,
|
|
31
|
+
password_iterations INTEGER,
|
|
32
|
+
password_salt TEXT,
|
|
33
|
+
password_hash TEXT,
|
|
34
|
+
credential_revision INTEGER NOT NULL DEFAULT 1,
|
|
35
|
+
created_by_account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
36
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
37
|
+
CHECK (
|
|
38
|
+
(mode = 'password' AND password_algorithm IS NOT NULL AND password_iterations IS NOT NULL
|
|
39
|
+
AND password_salt IS NOT NULL AND password_hash IS NOT NULL)
|
|
40
|
+
OR
|
|
41
|
+
(mode <> 'password' AND password_algorithm IS NULL AND password_iterations IS NULL
|
|
42
|
+
AND password_salt IS NULL AND password_hash IS NULL)
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
47
|
+
id TEXT PRIMARY KEY,
|
|
48
|
+
account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
49
|
+
name TEXT NOT NULL,
|
|
50
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
51
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
52
|
+
last_used_at TIMESTAMPTZ,
|
|
53
|
+
revoked_at TIMESTAMPTZ
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS drafts (
|
|
57
|
+
id TEXT PRIMARY KEY,
|
|
58
|
+
account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
59
|
+
title TEXT NOT NULL,
|
|
60
|
+
description TEXT,
|
|
61
|
+
current_version_id TEXT,
|
|
62
|
+
repo_org TEXT,
|
|
63
|
+
repo_name TEXT,
|
|
64
|
+
repo_host TEXT,
|
|
65
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
66
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
67
|
+
deleted_at TIMESTAMPTZ,
|
|
68
|
+
disabled_at TIMESTAMPTZ,
|
|
69
|
+
disabled_reason TEXT,
|
|
70
|
+
default_access_policy_id TEXT REFERENCES draft_access_policies(id)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS draft_versions (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
draft_id TEXT NOT NULL REFERENCES drafts(id),
|
|
76
|
+
version_number INTEGER NOT NULL,
|
|
77
|
+
object_key TEXT NOT NULL,
|
|
78
|
+
content_hash TEXT NOT NULL,
|
|
79
|
+
file_size INTEGER NOT NULL,
|
|
80
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
81
|
+
created_by_api_key_id TEXT NOT NULL REFERENCES api_keys(id),
|
|
82
|
+
source_ip TEXT,
|
|
83
|
+
user_agent TEXT,
|
|
84
|
+
cli_version TEXT,
|
|
85
|
+
git_branch TEXT,
|
|
86
|
+
git_commit_sha TEXT,
|
|
87
|
+
git_commit_subject TEXT,
|
|
88
|
+
git_dirty BOOLEAN,
|
|
89
|
+
original_filename TEXT,
|
|
90
|
+
request_id TEXT,
|
|
91
|
+
has_inline_script BOOLEAN,
|
|
92
|
+
external_image_hosts JSONB,
|
|
93
|
+
ci_run_url TEXT,
|
|
94
|
+
ci_actor TEXT,
|
|
95
|
+
access_policy_id TEXT REFERENCES draft_access_policies(id),
|
|
96
|
+
UNIQUE (draft_id, version_number)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE TABLE IF NOT EXISTS upload_events (
|
|
100
|
+
id TEXT PRIMARY KEY,
|
|
101
|
+
draft_id TEXT NOT NULL REFERENCES drafts(id),
|
|
102
|
+
draft_version_id TEXT REFERENCES draft_versions(id),
|
|
103
|
+
api_key_id TEXT NOT NULL REFERENCES api_keys(id),
|
|
104
|
+
event_type TEXT NOT NULL,
|
|
105
|
+
source_ip TEXT,
|
|
106
|
+
user_agent TEXT,
|
|
107
|
+
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
108
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE TABLE IF NOT EXISTS identities (
|
|
112
|
+
id TEXT PRIMARY KEY,
|
|
113
|
+
account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
114
|
+
provider TEXT NOT NULL,
|
|
115
|
+
subject TEXT NOT NULL,
|
|
116
|
+
email TEXT,
|
|
117
|
+
email_verified BOOLEAN,
|
|
118
|
+
display_name TEXT,
|
|
119
|
+
picture_url TEXT,
|
|
120
|
+
pii_subject TEXT,
|
|
121
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
122
|
+
last_login_at TIMESTAMPTZ,
|
|
123
|
+
UNIQUE (provider, subject)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
CREATE TABLE IF NOT EXISTS account_access_grants (
|
|
127
|
+
viewer_account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
128
|
+
resource_account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
129
|
+
created_by_api_key_id TEXT NOT NULL REFERENCES api_keys(id),
|
|
130
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
131
|
+
PRIMARY KEY (viewer_account_id, resource_account_id),
|
|
132
|
+
CHECK (viewer_account_id <> resource_account_id)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
CREATE TABLE IF NOT EXISTS access_policy_events (
|
|
136
|
+
id TEXT PRIMARY KEY,
|
|
137
|
+
draft_id TEXT NOT NULL REFERENCES drafts(id),
|
|
138
|
+
draft_version_id TEXT REFERENCES draft_versions(id),
|
|
139
|
+
actor_account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
140
|
+
actor_api_key_id TEXT REFERENCES api_keys(id),
|
|
141
|
+
previous_mode TEXT,
|
|
142
|
+
new_mode TEXT NOT NULL,
|
|
143
|
+
policy_id TEXT REFERENCES draft_access_policies(id),
|
|
144
|
+
credential_revision INTEGER,
|
|
145
|
+
request_id TEXT,
|
|
146
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE TABLE IF NOT EXISTS site_settings (
|
|
150
|
+
key TEXT PRIMARY KEY,
|
|
151
|
+
enabled BOOLEAN NOT NULL,
|
|
152
|
+
updated_by_account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
153
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
CREATE TABLE IF NOT EXISTS admin_events (
|
|
157
|
+
id TEXT PRIMARY KEY,
|
|
158
|
+
actor_account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
159
|
+
event_type TEXT NOT NULL,
|
|
160
|
+
setting_key TEXT NOT NULL,
|
|
161
|
+
previous_enabled BOOLEAN,
|
|
162
|
+
new_enabled BOOLEAN NOT NULL,
|
|
163
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
CREATE INDEX IF NOT EXISTS draft_versions_draft_id_idx ON draft_versions(draft_id);
|
|
167
|
+
CREATE INDEX IF NOT EXISTS upload_events_draft_id_idx ON upload_events(draft_id);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS drafts_account_id_idx ON drafts(account_id);
|
|
169
|
+
CREATE INDEX IF NOT EXISTS account_access_grants_resource_idx ON account_access_grants(resource_account_id, viewer_account_id);
|
|
170
|
+
CREATE INDEX IF NOT EXISTS access_policy_events_draft_idx ON access_policy_events(draft_id, created_at DESC);
|
|
171
|
+
CREATE INDEX IF NOT EXISTS admin_events_setting_idx ON admin_events(setting_key, created_at DESC);
|
|
172
|
+
|
|
173
|
+
-- Backfill columns for databases created before they were introduced.
|
|
174
|
+
ALTER TABLE identities ADD COLUMN IF NOT EXISTS email TEXT;
|
|
175
|
+
ALTER TABLE identities ADD COLUMN IF NOT EXISTS email_verified BOOLEAN;
|
|
176
|
+
ALTER TABLE identities ADD COLUMN IF NOT EXISTS display_name TEXT;
|
|
177
|
+
ALTER TABLE identities ADD COLUMN IF NOT EXISTS picture_url TEXT;
|
|
178
|
+
ALTER TABLE identities ADD COLUMN IF NOT EXISTS pii_subject TEXT;
|
|
179
|
+
ALTER TABLE drafts ADD COLUMN IF NOT EXISTS description TEXT;
|
|
180
|
+
ALTER TABLE drafts ADD COLUMN IF NOT EXISTS repo_host TEXT;
|
|
181
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS git_commit_subject TEXT;
|
|
182
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS git_dirty BOOLEAN;
|
|
183
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS request_id TEXT;
|
|
184
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS has_inline_script BOOLEAN;
|
|
185
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS external_image_hosts JSONB;
|
|
186
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS ci_run_url TEXT;
|
|
187
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS ci_actor TEXT;
|
|
188
|
+
ALTER TABLE drafts ADD COLUMN IF NOT EXISTS default_access_policy_id TEXT REFERENCES draft_access_policies(id);
|
|
189
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS access_policy_id TEXT REFERENCES draft_access_policies(id);
|
|
190
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'ready';
|
|
191
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS failure_reason TEXT;
|
|
192
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS finalized_at TIMESTAMPTZ;
|
|
193
|
+
ALTER TABLE draft_versions ADD COLUMN IF NOT EXISTS draft_metadata_json JSONB;
|
|
194
|
+
ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS parent_api_key_id TEXT REFERENCES api_keys(id);
|
|
195
|
+
CREATE INDEX IF NOT EXISTS api_keys_parent_idx ON api_keys(parent_api_key_id);
|
|
196
|
+
CREATE INDEX IF NOT EXISTS draft_versions_status_idx ON draft_versions(status, created_at);
|
|
197
|
+
`);
|
|
198
|
+
await ensurePublicUploadApiKey();
|
|
199
|
+
}
|
|
200
|
+
export async function ensureBootstrapApiKey() {
|
|
201
|
+
if (!config.bootstrapApiKey)
|
|
202
|
+
return;
|
|
203
|
+
const accountId = "acct_bootstrap";
|
|
204
|
+
const apiKeyId = "key_bootstrap";
|
|
205
|
+
const keyHash = sha256(config.bootstrapApiKey);
|
|
206
|
+
await withTransaction(async (client) => {
|
|
207
|
+
// The account upsert locks the same row used by API mint/revoke operations.
|
|
208
|
+
await client.query(`
|
|
209
|
+
INSERT INTO accounts (id, name) VALUES ($1, $2)
|
|
210
|
+
ON CONFLICT (id) DO UPDATE SET updated_at = now()
|
|
211
|
+
`, [accountId, "Bootstrap Account"]);
|
|
212
|
+
const previous = await client.query("SELECT key_hash FROM api_keys WHERE id = $1", [apiKeyId]);
|
|
213
|
+
if (previous.rows[0] && previous.rows[0].key_hash !== keyHash) {
|
|
214
|
+
const descendants = `WITH RECURSIVE descendants(id) AS (
|
|
215
|
+
SELECT id FROM api_keys WHERE id = $1 AND account_id = $2
|
|
216
|
+
UNION
|
|
217
|
+
SELECT k.id FROM api_keys k JOIN descendants d ON k.parent_api_key_id = d.id
|
|
218
|
+
WHERE k.account_id = $2
|
|
219
|
+
)`;
|
|
220
|
+
// Remove grants before restoring the root so prior root grants cannot revive.
|
|
221
|
+
await client.query(`${descendants}
|
|
222
|
+
DELETE FROM account_access_grants
|
|
223
|
+
WHERE created_by_api_key_id IN (SELECT id FROM descendants)
|
|
224
|
+
RETURNING created_by_api_key_id
|
|
225
|
+
`, [apiKeyId, accountId]);
|
|
226
|
+
await client.query(`${descendants}
|
|
227
|
+
UPDATE api_keys SET revoked_at = now()
|
|
228
|
+
WHERE id IN (SELECT id FROM descendants) AND revoked_at IS NULL
|
|
229
|
+
RETURNING id
|
|
230
|
+
`, [apiKeyId, accountId]);
|
|
231
|
+
}
|
|
232
|
+
await client.query(`
|
|
233
|
+
INSERT INTO api_keys (id, account_id, name, key_hash)
|
|
234
|
+
VALUES ($1, $2, $3, $4)
|
|
235
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
236
|
+
key_hash = EXCLUDED.key_hash,
|
|
237
|
+
name = EXCLUDED.name,
|
|
238
|
+
revoked_at = CASE WHEN api_keys.key_hash <> EXCLUDED.key_hash
|
|
239
|
+
THEN NULL ELSE api_keys.revoked_at END
|
|
240
|
+
`, [apiKeyId, accountId, "Bootstrap API Key", keyHash]);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
export async function findApiKeyByToken(token) {
|
|
244
|
+
const keyHash = sha256(token);
|
|
245
|
+
const result = await pool.query(`
|
|
246
|
+
SELECT api_keys.id, api_keys.account_id, api_keys.name, accounts.name AS account_name
|
|
247
|
+
FROM api_keys
|
|
248
|
+
JOIN accounts ON accounts.id = api_keys.account_id
|
|
249
|
+
WHERE api_keys.key_hash = $1
|
|
250
|
+
AND api_keys.id <> $2
|
|
251
|
+
AND api_keys.revoked_at IS NULL
|
|
252
|
+
LIMIT 1
|
|
253
|
+
`, [keyHash, publicUploadAuth.id]);
|
|
254
|
+
const apiKey = result.rows[0] || null;
|
|
255
|
+
if (apiKey) {
|
|
256
|
+
Object.defineProperty(apiKey, "keyHash", { value: keyHash, enumerable: false });
|
|
257
|
+
await pool.query("UPDATE api_keys SET last_used_at = now() WHERE id = $1", [apiKey.id]);
|
|
258
|
+
}
|
|
259
|
+
return apiKey;
|
|
260
|
+
}
|
|
261
|
+
async function ensurePublicUploadApiKey() {
|
|
262
|
+
await pool.query(`
|
|
263
|
+
INSERT INTO accounts (id, name)
|
|
264
|
+
VALUES ($1, $2)
|
|
265
|
+
ON CONFLICT (id) DO UPDATE
|
|
266
|
+
SET name = EXCLUDED.name,
|
|
267
|
+
updated_at = now()
|
|
268
|
+
`, [publicUploadAuth.account_id, publicUploadAuth.account_name]);
|
|
269
|
+
await pool.query(`
|
|
270
|
+
INSERT INTO api_keys (id, account_id, name, key_hash)
|
|
271
|
+
VALUES ($1, $2, $3, $4)
|
|
272
|
+
ON CONFLICT (id) DO UPDATE
|
|
273
|
+
SET key_hash = EXCLUDED.key_hash,
|
|
274
|
+
name = EXCLUDED.name,
|
|
275
|
+
revoked_at = NULL
|
|
276
|
+
`, [
|
|
277
|
+
publicUploadAuth.id,
|
|
278
|
+
publicUploadAuth.account_id,
|
|
279
|
+
publicUploadAuth.name,
|
|
280
|
+
sha256("openplan-public-upload-sentinel")
|
|
281
|
+
]);
|
|
282
|
+
}
|
|
283
|
+
export async function withTransaction(work) {
|
|
284
|
+
return database.transaction(work);
|
|
285
|
+
}
|
|
286
|
+
export function newEventId() {
|
|
287
|
+
return newInternalId();
|
|
288
|
+
}
|
|
289
|
+
export async function findOrCreateAccountForIdentity({ provider, subject, profile = {} }) {
|
|
290
|
+
try {
|
|
291
|
+
return await upsertIdentity({ provider, subject, profile });
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
// Two concurrent first sign-ins (e.g. two devices) can both miss the
|
|
295
|
+
// SELECT and collide on UNIQUE(provider, subject). The loser's transaction
|
|
296
|
+
// rolled back entirely (including its orphan account), so one retry hits
|
|
297
|
+
// the existing-identity path and succeeds.
|
|
298
|
+
if (isPgError(error) && error.code === "23505") {
|
|
299
|
+
return upsertIdentity({ provider, subject, profile });
|
|
300
|
+
}
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function upsertIdentity({ provider, subject, profile = {} }) {
|
|
305
|
+
const profileParams = [
|
|
306
|
+
profile.email ?? null,
|
|
307
|
+
profile.emailVerified ?? null,
|
|
308
|
+
profile.displayName ?? null,
|
|
309
|
+
profile.pictureUrl ?? null,
|
|
310
|
+
profile.piiSubject ?? null
|
|
311
|
+
];
|
|
312
|
+
return withTransaction(async (client) => {
|
|
313
|
+
const existing = await client.query(`
|
|
314
|
+
SELECT identities.account_id, accounts.name AS account_name
|
|
315
|
+
FROM identities
|
|
316
|
+
JOIN accounts ON accounts.id = identities.account_id
|
|
317
|
+
WHERE identities.provider = $1 AND identities.subject = $2
|
|
318
|
+
LIMIT 1
|
|
319
|
+
`, [provider, subject]);
|
|
320
|
+
if (existing.rows[0]) {
|
|
321
|
+
const accountId = existing.rows[0].account_id;
|
|
322
|
+
// The verified token is authoritative for profile fields: a claim absent
|
|
323
|
+
// from this login (e.g. the user removed their Google picture) clears the
|
|
324
|
+
// stored value, so DB, session, and header always agree. pii_subject is
|
|
325
|
+
// the one exception — it is a stable identifier, not editable profile,
|
|
326
|
+
// and a transient absence must not unlink the account.
|
|
327
|
+
await client.query(`
|
|
328
|
+
UPDATE identities
|
|
329
|
+
SET last_login_at = now(),
|
|
330
|
+
email = $3,
|
|
331
|
+
email_verified = $4,
|
|
332
|
+
display_name = $5,
|
|
333
|
+
picture_url = $6,
|
|
334
|
+
pii_subject = COALESCE($7, pii_subject)
|
|
335
|
+
WHERE provider = $1 AND subject = $2
|
|
336
|
+
`, [provider, subject, ...profileParams]);
|
|
337
|
+
// Same authoritative-token rule for the derived account name: if the
|
|
338
|
+
// user clears their Google name/email, the account label degrades to the
|
|
339
|
+
// neutral fallback instead of retaining old PII.
|
|
340
|
+
const accountName = profile.displayName || profile.email || `Openplan ${subject.slice(-6)}`;
|
|
341
|
+
if (accountName !== existing.rows[0].account_name) {
|
|
342
|
+
await client.query("UPDATE accounts SET name = $2, updated_at = now() WHERE id = $1", [
|
|
343
|
+
accountId,
|
|
344
|
+
accountName
|
|
345
|
+
]);
|
|
346
|
+
}
|
|
347
|
+
return { accountId, accountName, email: profile.email ?? null, pictureUrl: profile.pictureUrl ?? null };
|
|
348
|
+
}
|
|
349
|
+
const accountId = `acct_${newInternalId()}`;
|
|
350
|
+
const accountName = profile.displayName || profile.email || `Openplan ${subject.slice(-6)}`;
|
|
351
|
+
await client.query("INSERT INTO accounts (id, name) VALUES ($1, $2)", [
|
|
352
|
+
accountId,
|
|
353
|
+
accountName
|
|
354
|
+
]);
|
|
355
|
+
await client.query(`
|
|
356
|
+
INSERT INTO identities (
|
|
357
|
+
id, account_id, provider, subject,
|
|
358
|
+
email, email_verified, display_name, picture_url, pii_subject,
|
|
359
|
+
last_login_at
|
|
360
|
+
)
|
|
361
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now())
|
|
362
|
+
`, [newInternalId(), accountId, provider, subject, ...profileParams]);
|
|
363
|
+
return { accountId, accountName, email: profile.email ?? null, pictureUrl: profile.pictureUrl ?? null };
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function isPgError(error) {
|
|
367
|
+
return error instanceof Error && "code" in error && typeof error.code === "string";
|
|
368
|
+
}
|
|
369
|
+
/** Revoking a key also invalidates every key minted through it. */
|
|
370
|
+
export async function revokeApiKey(accountId, apiKeyId) {
|
|
371
|
+
// Serialize account-level mint/revoke operations, including concurrent siblings.
|
|
372
|
+
return withTransaction(async (client) => {
|
|
373
|
+
await client.query("SELECT id FROM accounts WHERE id = $1 FOR UPDATE", [accountId]);
|
|
374
|
+
const result = await client.query(`
|
|
375
|
+
WITH RECURSIVE descendants(id) AS (
|
|
376
|
+
SELECT id FROM api_keys WHERE id = $1 AND account_id = $2
|
|
377
|
+
UNION
|
|
378
|
+
SELECT k.id FROM api_keys k JOIN descendants d ON k.parent_api_key_id = d.id
|
|
379
|
+
WHERE k.account_id = $2
|
|
380
|
+
)
|
|
381
|
+
UPDATE api_keys SET revoked_at = now()
|
|
382
|
+
WHERE id IN (SELECT id FROM descendants) AND revoked_at IS NULL
|
|
383
|
+
RETURNING id
|
|
384
|
+
`, [apiKeyId, accountId]);
|
|
385
|
+
return result.rowCount > 0;
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/dist/drafts.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { QueryRow } from "./database.js";
|
|
2
|
+
export type DateValue = Date | string;
|
|
3
|
+
export interface DraftSummary {
|
|
4
|
+
draftId: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string | null;
|
|
7
|
+
repoOrg: string | null;
|
|
8
|
+
repoName: string | null;
|
|
9
|
+
repoHost: string | null;
|
|
10
|
+
latestVersionNumber: number | null;
|
|
11
|
+
versionCount: number;
|
|
12
|
+
createdAt: DateValue;
|
|
13
|
+
updatedAt: DateValue;
|
|
14
|
+
latestVersionAt: DateValue | null;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
publicUrl: string;
|
|
17
|
+
rawUrl: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DraftVersion extends QueryRow {
|
|
20
|
+
id: string;
|
|
21
|
+
version_number: number;
|
|
22
|
+
created_at: DateValue;
|
|
23
|
+
git_branch: string | null;
|
|
24
|
+
git_commit_sha: string | null;
|
|
25
|
+
git_commit_subject: string | null;
|
|
26
|
+
git_dirty: boolean | null;
|
|
27
|
+
file_size: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DraftDetailResult {
|
|
30
|
+
draft: {
|
|
31
|
+
draftId: string;
|
|
32
|
+
title: string;
|
|
33
|
+
description: string | null;
|
|
34
|
+
publicUrl: string;
|
|
35
|
+
};
|
|
36
|
+
versions: DraftVersion[];
|
|
37
|
+
}
|
|
38
|
+
export declare function listAccountDrafts(accountId: string, { requestBaseUrl }: {
|
|
39
|
+
requestBaseUrl: string;
|
|
40
|
+
}): Promise<DraftSummary[]>;
|
|
41
|
+
export declare function getAccountDraftWithVersions(accountId: string, draftId: string, { requestBaseUrl }: {
|
|
42
|
+
requestBaseUrl: string;
|
|
43
|
+
}): Promise<DraftDetailResult | null>;
|
package/dist/drafts.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { pool } from "./db.js";
|
|
2
|
+
import { config } from "./config.js";
|
|
3
|
+
import { getDraftPublicUrl, getDraftRawUrl } from "./public-url.js";
|
|
4
|
+
// The "my docs" feed: every draft owned by an account, newest first, with the
|
|
5
|
+
// aggregates a dashboard needs (latest version, version count, repo). Shared
|
|
6
|
+
// by GET /api/drafts and the server-rendered dashboard.
|
|
7
|
+
export async function listAccountDrafts(accountId, { requestBaseUrl }) {
|
|
8
|
+
const result = await pool.query(`
|
|
9
|
+
SELECT
|
|
10
|
+
d.id,
|
|
11
|
+
d.title,
|
|
12
|
+
d.description,
|
|
13
|
+
d.repo_org,
|
|
14
|
+
d.repo_name,
|
|
15
|
+
d.repo_host,
|
|
16
|
+
d.created_at,
|
|
17
|
+
d.updated_at,
|
|
18
|
+
d.disabled_at,
|
|
19
|
+
cv.version_number AS latest_version_number,
|
|
20
|
+
cv.created_at AS latest_version_at,
|
|
21
|
+
COALESCE(vc.version_count, 0) AS version_count
|
|
22
|
+
FROM drafts d
|
|
23
|
+
LEFT JOIN draft_versions cv ON cv.id = d.current_version_id
|
|
24
|
+
LEFT JOIN (
|
|
25
|
+
SELECT draft_id, COUNT(*)::int AS version_count
|
|
26
|
+
FROM draft_versions
|
|
27
|
+
WHERE status = 'ready'
|
|
28
|
+
GROUP BY draft_id
|
|
29
|
+
) vc ON vc.draft_id = d.id
|
|
30
|
+
WHERE d.account_id = $1
|
|
31
|
+
AND d.deleted_at IS NULL
|
|
32
|
+
ORDER BY d.updated_at DESC
|
|
33
|
+
`, [accountId]);
|
|
34
|
+
return result.rows.map((row) => ({
|
|
35
|
+
draftId: row.id,
|
|
36
|
+
title: row.title,
|
|
37
|
+
description: row.description,
|
|
38
|
+
repoOrg: row.repo_org,
|
|
39
|
+
repoName: row.repo_name,
|
|
40
|
+
repoHost: row.repo_host,
|
|
41
|
+
latestVersionNumber: row.latest_version_number === null ? null : Number(row.latest_version_number),
|
|
42
|
+
versionCount: Number(row.version_count),
|
|
43
|
+
createdAt: row.created_at,
|
|
44
|
+
updatedAt: row.updated_at,
|
|
45
|
+
latestVersionAt: row.latest_version_at,
|
|
46
|
+
disabled: Boolean(row.disabled_at),
|
|
47
|
+
publicUrl: getDraftPublicUrl({
|
|
48
|
+
draftId: row.id,
|
|
49
|
+
publicBaseUrl: config.publicBaseUrl,
|
|
50
|
+
requestBaseUrl
|
|
51
|
+
}),
|
|
52
|
+
rawUrl: getDraftRawUrl({
|
|
53
|
+
draftId: row.id,
|
|
54
|
+
publicBaseUrl: config.publicBaseUrl,
|
|
55
|
+
requestBaseUrl
|
|
56
|
+
})
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
export async function getAccountDraftWithVersions(accountId, draftId, { requestBaseUrl }) {
|
|
60
|
+
const draftResult = await pool.query(`
|
|
61
|
+
SELECT *
|
|
62
|
+
FROM drafts
|
|
63
|
+
WHERE id = $1 AND account_id = $2 AND deleted_at IS NULL
|
|
64
|
+
LIMIT 1
|
|
65
|
+
`, [draftId, accountId]);
|
|
66
|
+
const draft = draftResult.rows[0];
|
|
67
|
+
if (!draft)
|
|
68
|
+
return null;
|
|
69
|
+
const versionsResult = await pool.query(`
|
|
70
|
+
SELECT id, version_number, created_at, git_branch, git_commit_sha,
|
|
71
|
+
git_commit_subject, git_dirty, file_size
|
|
72
|
+
FROM draft_versions
|
|
73
|
+
WHERE draft_id = $1 AND status = 'ready'
|
|
74
|
+
ORDER BY version_number DESC
|
|
75
|
+
`, [draftId]);
|
|
76
|
+
return {
|
|
77
|
+
draft: {
|
|
78
|
+
draftId: draft.id,
|
|
79
|
+
title: draft.title,
|
|
80
|
+
description: draft.description,
|
|
81
|
+
publicUrl: getDraftPublicUrl({
|
|
82
|
+
draftId: draft.id,
|
|
83
|
+
publicBaseUrl: config.publicBaseUrl,
|
|
84
|
+
requestBaseUrl
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
versions: versionsResult.rows
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=drafts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../src/drafts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA+DpE,8EAA8E;AAC9E,6EAA6E;AAC7E,wDAAwD;AACxD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAiB,EACjB,EAAE,cAAc,EAA8B;IAE9C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;;;;;;;;;;;;;;;;;;;;;;;KAyBC,EACD,CAAC,SAAS,CAAC,CACZ,CAAC;IAEF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/B,OAAO,EAAE,GAAG,CAAC,EAAE;QACf,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,OAAO,EAAE,GAAG,CAAC,QAAQ;QACrB,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,mBAAmB,EACjB,GAAG,CAAC,qBAAqB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC;QAC/E,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;QACvC,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,eAAe,EAAE,GAAG,CAAC,iBAAiB;QACtC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;QAClC,SAAS,EAAE,iBAAiB,CAAC;YAC3B,OAAO,EAAE,GAAG,CAAC,EAAE;YACf,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc;SACf,CAAC;QACF,MAAM,EAAE,cAAc,CAAC;YACrB,OAAO,EAAE,GAAG,CAAC,EAAE;YACf,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc;SACf,CAAC;KACH,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,SAAiB,EACjB,OAAe,EACf,EAAE,cAAc,EAA8B;IAE9C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,KAAK,CAClC;;;;;KAKC,EACD,CAAC,OAAO,EAAE,SAAS,CAAC,CACrB,CAAC;IACF,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,KAAK,CACrC;;;;;;KAMC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO;QACL,KAAK,EAAE;YACL,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,iBAAiB,CAAC;gBAC3B,OAAO,EAAE,KAAK,CAAC,EAAE;gBACjB,aAAa,EAAE,MAAM,CAAC,aAAa;gBACnC,cAAc;aACf,CAAC;SACH;QACD,QAAQ,EAAE,cAAc,CAAC,IAAI;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface FeatureSetting {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
updatedByAccountId: string | null;
|
|
4
|
+
updatedAt: Date | string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function getBrowserLocalAnnotationsSetting(): Promise<FeatureSetting>;
|
|
7
|
+
export declare function setBrowserLocalAnnotationsEnabled(actorAccountId: string, enabled: boolean): Promise<FeatureSetting>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { newEventId, pool, withTransaction } from "./db.js";
|
|
2
|
+
const ANNOTATIONS_KEY = "browser_local_annotations";
|
|
3
|
+
export async function getBrowserLocalAnnotationsSetting() {
|
|
4
|
+
const result = await pool.query("SELECT enabled, updated_by_account_id, updated_at FROM site_settings WHERE key = $1 LIMIT 1", [ANNOTATIONS_KEY]);
|
|
5
|
+
const row = result.rows[0];
|
|
6
|
+
return row
|
|
7
|
+
? { enabled: row.enabled, updatedByAccountId: row.updated_by_account_id, updatedAt: row.updated_at }
|
|
8
|
+
: { enabled: false, updatedByAccountId: null, updatedAt: null };
|
|
9
|
+
}
|
|
10
|
+
export async function setBrowserLocalAnnotationsEnabled(actorAccountId, enabled) {
|
|
11
|
+
return withTransaction(async (client) => {
|
|
12
|
+
const previous = await client.query("SELECT enabled, updated_by_account_id, updated_at FROM site_settings WHERE key = $1 FOR UPDATE", [ANNOTATIONS_KEY]);
|
|
13
|
+
const previousEnabled = previous.rows[0]?.enabled ?? null;
|
|
14
|
+
const result = await client.query(`
|
|
15
|
+
INSERT INTO site_settings (key, enabled, updated_by_account_id)
|
|
16
|
+
VALUES ($1, $2, $3)
|
|
17
|
+
ON CONFLICT (key) DO UPDATE
|
|
18
|
+
SET enabled = EXCLUDED.enabled,
|
|
19
|
+
updated_by_account_id = EXCLUDED.updated_by_account_id,
|
|
20
|
+
updated_at = now()
|
|
21
|
+
RETURNING enabled, updated_by_account_id, updated_at
|
|
22
|
+
`, [ANNOTATIONS_KEY, enabled, actorAccountId]);
|
|
23
|
+
await client.query(`
|
|
24
|
+
INSERT INTO admin_events (
|
|
25
|
+
id, actor_account_id, event_type, setting_key, previous_enabled, new_enabled
|
|
26
|
+
) VALUES ($1, $2, $3, $4, $5, $6)
|
|
27
|
+
`, [newEventId(), actorAccountId, "feature_setting_changed", ANNOTATIONS_KEY, previousEnabled, enabled]);
|
|
28
|
+
const row = result.rows[0];
|
|
29
|
+
return { enabled: row.enabled, updatedByAccountId: row.updated_by_account_id, updatedAt: row.updated_at };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=feature-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-settings.js","sourceRoot":"","sources":["../src/feature-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAG5D,MAAM,eAAe,GAAG,2BAA2B,CAAC;AAcpD,MAAM,CAAC,KAAK,UAAU,iCAAiC;IACrD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,6FAA6F,EAC7F,CAAC,eAAe,CAAC,CAClB,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,GAAG;QACR,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,CAAC,qBAAqB,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE;QACpG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACrD,cAAsB,EACtB,OAAgB;IAEhB,OAAO,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QACtC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CACjC,gGAAgG,EAChG,CAAC,eAAe,CAAC,CAClB,CAAC;QACF,MAAM,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;QAC1D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B;;;;;;;;OAQC,EACD,CAAC,eAAe,EAAE,OAAO,EAAE,cAAc,CAAC,CAC3C,CAAC;QACF,MAAM,MAAM,CAAC,KAAK,CAChB;;;;OAIC,EACD,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,yBAAyB,EAAE,eAAe,EAAE,eAAe,EAAE,OAAO,CAAC,CACrG,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,CAAC,qBAAqB,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;IAC5G,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ANNOTATION_CLIENT: string;
|