@axium/server 0.27.0 → 0.28.1
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/acl.d.ts +26 -46
- package/dist/acl.js +42 -62
- package/dist/api/acl.js +11 -3
- package/dist/api/admin.js +3 -6
- package/dist/api/metadata.js +4 -11
- package/dist/audit.d.ts +0 -3
- package/dist/audit.js +0 -8
- package/dist/auth.d.ts +10 -5
- package/dist/auth.js +29 -23
- package/dist/cli.d.ts +8 -2
- package/dist/cli.js +18 -606
- package/dist/config.js +6 -5
- package/dist/database.d.ts +413 -29
- package/dist/database.js +522 -247
- package/dist/db.json +71 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +833 -0
- package/package.json +6 -4
- package/routes/account/+page.svelte +11 -13
- package/routes/admin/config/+page.svelte +2 -2
- package/routes/admin/plugins/+page.svelte +41 -4
- package/schemas/config.json +207 -0
- package/schemas/db.json +636 -0
package/dist/database.js
CHANGED
|
@@ -55,10 +55,14 @@ import { plugins } from '@axium/core/plugins';
|
|
|
55
55
|
import { Kysely, PostgresDialect, sql } from 'kysely';
|
|
56
56
|
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
|
57
57
|
import { randomBytes } from 'node:crypto';
|
|
58
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
58
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
59
|
+
import { join } from 'node:path/posix';
|
|
60
|
+
import { styleText } from 'node:util';
|
|
59
61
|
import pg from 'pg';
|
|
62
|
+
import * as z from 'zod';
|
|
60
63
|
import config from './config.js';
|
|
61
|
-
import
|
|
64
|
+
import rawSchema from './db.json' with { type: 'json' };
|
|
65
|
+
import { dirs, systemDir } from './io.js';
|
|
62
66
|
const sym = Symbol.for('Axium:database');
|
|
63
67
|
export let database;
|
|
64
68
|
export function connect() {
|
|
@@ -157,26 +161,16 @@ export async function getHBA(opt) {
|
|
|
157
161
|
};
|
|
158
162
|
return [content, writeBack];
|
|
159
163
|
}
|
|
160
|
-
|
|
164
|
+
/** @internal @hidden */
|
|
165
|
+
export const _pgHba = `
|
|
161
166
|
local axium axium md5
|
|
162
167
|
host axium axium 127.0.0.1/32 md5
|
|
163
168
|
host axium axium ::1/128 md5
|
|
164
169
|
`;
|
|
165
|
-
|
|
170
|
+
/** @internal @hidden */
|
|
171
|
+
export const _sql = (command, message) => io.run(message, `sudo -u postgres psql -c "${command}"`);
|
|
166
172
|
/** Shortcut to output a warning if an error is thrown because relation already exists */
|
|
167
173
|
export const warnExists = io.someWarnings([/\w+ "[\w.]+" already exists/, 'already exists.']);
|
|
168
|
-
const throwUnlessRows = (text) => {
|
|
169
|
-
if (text.includes('(0 rows)'))
|
|
170
|
-
throw 'missing.';
|
|
171
|
-
return text;
|
|
172
|
-
};
|
|
173
|
-
export async function createIndex(table, column, mod) {
|
|
174
|
-
io.start(`Creating index for ${table}.${column}`);
|
|
175
|
-
let query = database.schema.createIndex(`${table}_${column}_index`).on(table).column(column);
|
|
176
|
-
if (mod)
|
|
177
|
-
query = mod(query);
|
|
178
|
-
await query.execute().then(io.done).catch(warnExists);
|
|
179
|
-
}
|
|
180
174
|
export async function init(opt) {
|
|
181
175
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
182
176
|
try {
|
|
@@ -210,11 +204,11 @@ export async function init(opt) {
|
|
|
210
204
|
await getHBA(opt)
|
|
211
205
|
.then(([content, writeBack]) => {
|
|
212
206
|
io.start('Checking for Axium HBA configuration');
|
|
213
|
-
if (content.includes(
|
|
207
|
+
if (content.includes(_pgHba))
|
|
214
208
|
throw 'already exists.';
|
|
215
209
|
io.done();
|
|
216
210
|
io.start('Adding Axium HBA configuration');
|
|
217
|
-
const newContent = content.replace(/^local\s+all\s+all.*$/m, `$&\n${
|
|
211
|
+
const newContent = content.replace(/^local\s+all\s+all.*$/m, `$&\n${_pgHba}`);
|
|
218
212
|
io.done();
|
|
219
213
|
writeBack(newContent);
|
|
220
214
|
})
|
|
@@ -223,91 +217,8 @@ export async function init(opt) {
|
|
|
223
217
|
io.start('Connecting to database');
|
|
224
218
|
const _ = __addDisposableResource(env_1, connect(), true);
|
|
225
219
|
io.done();
|
|
226
|
-
function maybeCheck(table) {
|
|
227
|
-
return (e) => {
|
|
228
|
-
warnExists(e);
|
|
229
|
-
if (opt.check)
|
|
230
|
-
return checkTableTypes(table, expectedTypes[table], opt);
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
io.start('Creating table users');
|
|
234
|
-
await database.schema
|
|
235
|
-
.createTable('users')
|
|
236
|
-
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
237
|
-
.addColumn('name', 'text')
|
|
238
|
-
.addColumn('email', 'text', col => col.unique().notNull())
|
|
239
|
-
.addColumn('emailVerified', 'timestamptz')
|
|
240
|
-
.addColumn('image', 'text')
|
|
241
|
-
.addColumn('isAdmin', 'boolean', col => col.notNull().defaultTo(false))
|
|
242
|
-
.addColumn('roles', sql `text[]`, col => col.notNull().defaultTo(sql `'{}'::text[]`))
|
|
243
|
-
.addColumn('tags', sql `text[]`, col => col.notNull().defaultTo(sql `'{}'::text[]`))
|
|
244
|
-
.addColumn('preferences', 'jsonb', col => col.notNull().defaultTo('{}'))
|
|
245
|
-
.addColumn('registeredAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
|
|
246
|
-
.addColumn('isSuspended', 'boolean', col => col.notNull().defaultTo(false))
|
|
247
|
-
.execute()
|
|
248
|
-
.then(io.done)
|
|
249
|
-
.catch(maybeCheck('users'));
|
|
250
|
-
io.start('Creating table sessions');
|
|
251
|
-
await database.schema
|
|
252
|
-
.createTable('sessions')
|
|
253
|
-
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
254
|
-
.addColumn('userId', 'uuid', col => col.references('users.id').onDelete('cascade').notNull())
|
|
255
|
-
.addColumn('token', 'text', col => col.notNull().unique())
|
|
256
|
-
.addColumn('created', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
|
|
257
|
-
.addColumn('expires', 'timestamptz', col => col.notNull())
|
|
258
|
-
.addColumn('elevated', 'boolean', col => col.notNull())
|
|
259
|
-
.execute()
|
|
260
|
-
.then(io.done)
|
|
261
|
-
.catch(maybeCheck('sessions'));
|
|
262
|
-
await createIndex('sessions', 'id');
|
|
263
|
-
io.start('Creating table verifications');
|
|
264
|
-
await database.schema
|
|
265
|
-
.createTable('verifications')
|
|
266
|
-
.addColumn('userId', 'uuid', col => col.references('users.id').onDelete('cascade').notNull())
|
|
267
|
-
.addColumn('token', 'text', col => col.notNull().unique())
|
|
268
|
-
.addColumn('expires', 'timestamptz', col => col.notNull())
|
|
269
|
-
.addColumn('role', 'text', col => col.notNull())
|
|
270
|
-
.execute()
|
|
271
|
-
.then(io.done)
|
|
272
|
-
.catch(maybeCheck('verifications'));
|
|
273
|
-
io.start('Creating table passkeys');
|
|
274
|
-
await database.schema
|
|
275
|
-
.createTable('passkeys')
|
|
276
|
-
.addColumn('id', 'text', col => col.primaryKey().notNull())
|
|
277
|
-
.addColumn('name', 'text')
|
|
278
|
-
.addColumn('createdAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
|
|
279
|
-
.addColumn('userId', 'uuid', col => col.notNull().references('users.id').onDelete('cascade').notNull())
|
|
280
|
-
.addColumn('publicKey', 'bytea', col => col.notNull())
|
|
281
|
-
.addColumn('counter', 'integer', col => col.notNull())
|
|
282
|
-
.addColumn('deviceType', 'text', col => col.notNull())
|
|
283
|
-
.addColumn('backedUp', 'boolean', col => col.notNull())
|
|
284
|
-
.addColumn('transports', sql `text[]`)
|
|
285
|
-
.execute()
|
|
286
|
-
.then(io.done)
|
|
287
|
-
.catch(maybeCheck('passkeys'));
|
|
288
|
-
await createIndex('passkeys', 'userId');
|
|
289
|
-
io.start('Creating table audit_log');
|
|
290
|
-
await database.schema
|
|
291
|
-
.createTable('audit_log')
|
|
292
|
-
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
293
|
-
.addColumn('timestamp', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
|
|
294
|
-
.addColumn('userId', 'uuid')
|
|
295
|
-
.addColumn('severity', 'integer', col => col.notNull())
|
|
296
|
-
.addColumn('name', 'text', col => col.notNull())
|
|
297
|
-
.addColumn('source', 'text', col => col.notNull())
|
|
298
|
-
.addColumn('tags', sql `text[]`, col => col.notNull().defaultTo(sql `'{}'::text[]`))
|
|
299
|
-
.addColumn('extra', 'jsonb', col => col.notNull().defaultTo('{}'))
|
|
300
|
-
.execute()
|
|
301
|
-
.then(io.done)
|
|
302
|
-
.catch(maybeCheck('audit_log'));
|
|
303
220
|
io.start('Creating schema acl');
|
|
304
221
|
await database.schema.createSchema('acl').execute().then(io.done).catch(warnExists);
|
|
305
|
-
for (const plugin of plugins.values()) {
|
|
306
|
-
if (!plugin._hooks?.db_init)
|
|
307
|
-
continue;
|
|
308
|
-
io.log(styleText('whiteBright', 'Running plugin: '), plugin.name);
|
|
309
|
-
await plugin._hooks?.db_init(opt);
|
|
310
|
-
}
|
|
311
222
|
}
|
|
312
223
|
catch (e_1) {
|
|
313
224
|
env_1.error = e_1;
|
|
@@ -319,85 +230,535 @@ export async function init(opt) {
|
|
|
319
230
|
await result_1;
|
|
320
231
|
}
|
|
321
232
|
}
|
|
322
|
-
export const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
233
|
+
export const Column = z.strictObject({
|
|
234
|
+
type: z.string(),
|
|
235
|
+
required: z.boolean().default(false),
|
|
236
|
+
unique: z.boolean().default(false),
|
|
237
|
+
primary: z.boolean().default(false),
|
|
238
|
+
references: z.string().optional(),
|
|
239
|
+
onDelete: z.enum(['cascade', 'restrict', 'no action', 'set null', 'set default']).optional(),
|
|
240
|
+
default: z.any().optional(),
|
|
241
|
+
check: z.string().optional(),
|
|
242
|
+
});
|
|
243
|
+
export const Constraint = z.discriminatedUnion('type', [
|
|
244
|
+
z.strictObject({
|
|
245
|
+
type: z.literal('primary_key'),
|
|
246
|
+
on: z.string().array(),
|
|
247
|
+
}),
|
|
248
|
+
z.strictObject({
|
|
249
|
+
type: z.literal('foreign_key'),
|
|
250
|
+
on: z.string().array(),
|
|
251
|
+
target: z.string(),
|
|
252
|
+
references: z.string().array(),
|
|
253
|
+
}),
|
|
254
|
+
z.strictObject({
|
|
255
|
+
type: z.literal('unique'),
|
|
256
|
+
on: z.string().array(),
|
|
257
|
+
nulls_not_distinct: z.boolean().optional(),
|
|
258
|
+
}),
|
|
259
|
+
z.strictObject({
|
|
260
|
+
type: z.literal('check'),
|
|
261
|
+
check: z.string(),
|
|
262
|
+
}),
|
|
263
|
+
]);
|
|
264
|
+
export const Table = z.strictObject({
|
|
265
|
+
columns: z.record(z.string(), Column),
|
|
266
|
+
constraints: z.record(z.string(), Constraint).optional().default({}),
|
|
267
|
+
});
|
|
268
|
+
export const IndexString = z.templateLiteral([z.string(), ':', z.string()]);
|
|
269
|
+
export function parseIndex(value) {
|
|
270
|
+
const [table, column] = value.split(':');
|
|
271
|
+
return { table, column };
|
|
272
|
+
}
|
|
273
|
+
export const SchemaDecl = z.strictObject({
|
|
274
|
+
tables: z.record(z.string(), Table),
|
|
275
|
+
indexes: IndexString.array().optional().default([]),
|
|
276
|
+
});
|
|
277
|
+
export const ColumnDelta = z.strictObject({
|
|
278
|
+
type: z.string().optional(),
|
|
279
|
+
default: z.string().optional(),
|
|
280
|
+
ops: z.literal(['drop_default', 'set_required', 'drop_required']).array().optional(),
|
|
281
|
+
});
|
|
282
|
+
export const TableDelta = z.strictObject({
|
|
283
|
+
add_columns: z.record(z.string(), Column).optional().default({}),
|
|
284
|
+
drop_columns: z.string().array().optional().default([]),
|
|
285
|
+
alter_columns: z.record(z.string(), ColumnDelta).optional().default({}),
|
|
286
|
+
add_constraints: z.record(z.string(), Constraint).optional().default({}),
|
|
287
|
+
drop_constraints: z.string().array().optional().default([]),
|
|
288
|
+
});
|
|
289
|
+
export const VersionDelta = z.strictObject({
|
|
290
|
+
delta: z.literal(true),
|
|
291
|
+
add_tables: z.record(z.string(), Table).optional().default({}),
|
|
292
|
+
drop_tables: z.string().array().optional().default([]),
|
|
293
|
+
alter_tables: z.record(z.string(), TableDelta).optional().default({}),
|
|
294
|
+
add_indexes: IndexString.array().optional().default([]),
|
|
295
|
+
drop_indexes: IndexString.array().optional().default([]),
|
|
296
|
+
});
|
|
297
|
+
export const SchemaFile = z.object({
|
|
298
|
+
format: z.literal(0),
|
|
299
|
+
versions: z.discriminatedUnion('delta', [SchemaDecl.extend({ delta: z.literal(false) }), VersionDelta]).array(),
|
|
300
|
+
/** List of tables to wipe */
|
|
301
|
+
wipe: z.string().array().optional().default([]),
|
|
302
|
+
/** Set the latest version, defaults to the last one */
|
|
303
|
+
latest: z.number().nonnegative().optional(),
|
|
304
|
+
/** Maps tables to their ACL tables, e.g. `"storage": "acl.storage"` */
|
|
305
|
+
acl_tables: z.record(z.string(), z.string()).optional().default({}),
|
|
306
|
+
});
|
|
307
|
+
const { data, error } = SchemaFile.safeParse(rawSchema);
|
|
308
|
+
if (error)
|
|
309
|
+
io.error('Invalid base database schema:\n' + z.prettifyError(error));
|
|
310
|
+
const schema = data;
|
|
311
|
+
export function* getSchemaFiles() {
|
|
312
|
+
yield ['@axium/server', schema];
|
|
313
|
+
for (const [name, plugin] of plugins) {
|
|
314
|
+
if (!plugin._db)
|
|
315
|
+
continue;
|
|
316
|
+
try {
|
|
317
|
+
yield [name, SchemaFile.parse(plugin._db)];
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
const text = e instanceof z.core.$ZodError ? z.prettifyError(e) : e instanceof Error ? e.message : String(e);
|
|
321
|
+
throw `Invalid database configuration for plugin "${name}":\n${text}`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get the active schema
|
|
327
|
+
*/
|
|
328
|
+
export function getFullSchema(opt = {}) {
|
|
329
|
+
const fullSchema = { tables: {}, indexes: [] };
|
|
330
|
+
for (const [pluginName, file] of getSchemaFiles()) {
|
|
331
|
+
if (opt.exclude?.includes(pluginName))
|
|
332
|
+
continue;
|
|
333
|
+
let currentSchema = { tables: {}, indexes: [] };
|
|
334
|
+
for (const [version, schema] of file.versions.entries()) {
|
|
335
|
+
if (schema.delta)
|
|
336
|
+
applyDeltaToSchema(currentSchema, schema);
|
|
337
|
+
else
|
|
338
|
+
currentSchema = schema;
|
|
339
|
+
if (version === file.latest)
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
for (const name of Object.keys(currentSchema.tables)) {
|
|
343
|
+
if (name in fullSchema.tables)
|
|
344
|
+
throw 'Duplicate table name in database schema: ' + name;
|
|
345
|
+
fullSchema.tables[name] = currentSchema.tables[name];
|
|
346
|
+
}
|
|
347
|
+
for (const index of currentSchema.indexes) {
|
|
348
|
+
if (fullSchema.indexes.includes(index))
|
|
349
|
+
throw 'Duplicate index in database schema: ' + index;
|
|
350
|
+
fullSchema.indexes.push(index);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return fullSchema;
|
|
354
|
+
}
|
|
355
|
+
const schemaToIntrospected = {
|
|
356
|
+
boolean: 'bool',
|
|
357
|
+
integer: 'int4',
|
|
358
|
+
'text[]': '_text',
|
|
371
359
|
};
|
|
360
|
+
const VersionMap = z.record(z.string(), z.int().nonnegative());
|
|
361
|
+
export const UpgradesInfo = z.object({
|
|
362
|
+
current: VersionMap.default({}),
|
|
363
|
+
upgrades: z.object({ timestamp: z.coerce.date(), from: VersionMap, to: VersionMap }).array().default([]),
|
|
364
|
+
});
|
|
365
|
+
const upgradesFilePath = process.getuid?.() == 0 ? join(systemDir, 'db_upgrades.json') : join(dirs.at(-1), 'db_upgrades.json');
|
|
366
|
+
export function getUpgradeInfo() {
|
|
367
|
+
if (!existsSync(upgradesFilePath))
|
|
368
|
+
io.writeJSON(upgradesFilePath, { current: {}, upgrades: [] });
|
|
369
|
+
return io.readJSON(upgradesFilePath, UpgradesInfo);
|
|
370
|
+
}
|
|
371
|
+
export function setUpgradeInfo(info) {
|
|
372
|
+
io.writeJSON(upgradesFilePath, info);
|
|
373
|
+
}
|
|
374
|
+
export function applyTableDeltaToSchema(table, delta) {
|
|
375
|
+
for (const column of delta.drop_columns) {
|
|
376
|
+
if (column in table)
|
|
377
|
+
delete table.columns[column];
|
|
378
|
+
else
|
|
379
|
+
throw `Can't drop column ${column} because it does not exist`;
|
|
380
|
+
}
|
|
381
|
+
for (const [name, column] of Object.entries(delta.add_columns)) {
|
|
382
|
+
if (name in table)
|
|
383
|
+
throw `Can't add column ${name} because it already exists`;
|
|
384
|
+
table.columns[name] = column;
|
|
385
|
+
}
|
|
386
|
+
for (const [name, columnDelta] of Object.entries(delta.alter_columns)) {
|
|
387
|
+
const column = table.columns[name];
|
|
388
|
+
if (!column)
|
|
389
|
+
throw `Can't modify column ${name} because it does not exist`;
|
|
390
|
+
if (columnDelta.type)
|
|
391
|
+
column.type = columnDelta.type;
|
|
392
|
+
if (columnDelta.default)
|
|
393
|
+
column.default = columnDelta.default;
|
|
394
|
+
for (const op of columnDelta.ops || []) {
|
|
395
|
+
switch (op) {
|
|
396
|
+
case 'drop_default':
|
|
397
|
+
delete column.default;
|
|
398
|
+
break;
|
|
399
|
+
case 'set_required':
|
|
400
|
+
column.required = true;
|
|
401
|
+
break;
|
|
402
|
+
case 'drop_required':
|
|
403
|
+
column.required = false;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
for (const name of delta.drop_constraints) {
|
|
409
|
+
if (table.constraints[name])
|
|
410
|
+
delete table.constraints[name];
|
|
411
|
+
else
|
|
412
|
+
throw `Can't drop constraint ${name} because it does not exist`;
|
|
413
|
+
}
|
|
414
|
+
for (const [name, constraint] of Object.entries(delta.add_constraints)) {
|
|
415
|
+
if (table.constraints[name])
|
|
416
|
+
throw `Can't add constraint ${name} because it already exists`;
|
|
417
|
+
table.constraints[name] = constraint;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
export function applyDeltaToSchema(schema, delta) {
|
|
421
|
+
for (const tableName of delta.drop_tables) {
|
|
422
|
+
if (tableName in schema.tables)
|
|
423
|
+
delete schema.tables[tableName];
|
|
424
|
+
else
|
|
425
|
+
throw `Can't drop table ${tableName} because it does not exist`;
|
|
426
|
+
}
|
|
427
|
+
for (const [tableName, table] of Object.entries(delta.add_tables)) {
|
|
428
|
+
if (tableName in schema.tables)
|
|
429
|
+
throw `Can't add table ${tableName} because it already exists`;
|
|
430
|
+
else
|
|
431
|
+
schema.tables[tableName] = table;
|
|
432
|
+
}
|
|
433
|
+
for (const [tableName, tableDelta] of Object.entries(delta.alter_tables)) {
|
|
434
|
+
if (tableName in schema.tables)
|
|
435
|
+
applyTableDeltaToSchema(schema.tables[tableName], tableDelta);
|
|
436
|
+
else
|
|
437
|
+
throw `Can't modify table ${tableName} because it does not exist`;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
export function validateDelta(delta) {
|
|
441
|
+
const tableNames = [...Object.keys(delta.add_tables), ...Object.keys(delta.alter_tables), delta.drop_tables];
|
|
442
|
+
const uniqueTables = new Set(tableNames);
|
|
443
|
+
for (const table of uniqueTables) {
|
|
444
|
+
tableNames.splice(tableNames.indexOf(table), 1);
|
|
445
|
+
}
|
|
446
|
+
if (tableNames.length) {
|
|
447
|
+
throw `Duplicate table name(s): ${tableNames.join(', ')}`;
|
|
448
|
+
}
|
|
449
|
+
for (const [tableName, table] of Object.entries(delta.alter_tables)) {
|
|
450
|
+
const columnNames = [...Object.keys(table.add_columns), ...table.drop_columns];
|
|
451
|
+
const uniqueColumns = new Set(columnNames);
|
|
452
|
+
for (const column of uniqueColumns) {
|
|
453
|
+
columnNames.splice(columnNames.indexOf(column), 1);
|
|
454
|
+
}
|
|
455
|
+
if (columnNames.length) {
|
|
456
|
+
throw `Duplicate column name(s) in table ${tableName}: ${columnNames.join(', ')}`;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
export function computeDelta(from, to) {
|
|
461
|
+
const fromTables = new Set(Object.keys(from.tables));
|
|
462
|
+
const toTables = new Set(Object.keys(to.tables));
|
|
463
|
+
const fromIndexes = new Set(from.indexes);
|
|
464
|
+
const toIndexes = new Set(to.indexes);
|
|
465
|
+
const add_tables = Object.fromEntries(toTables
|
|
466
|
+
.difference(fromTables)
|
|
467
|
+
.keys()
|
|
468
|
+
.map(name => [name, to.tables[name]]));
|
|
469
|
+
const alter_tables = {};
|
|
470
|
+
for (const name of fromTables.intersection(toTables)) {
|
|
471
|
+
const fromTable = from.tables[name], toTable = to.tables[name];
|
|
472
|
+
const fromColumns = new Set(Object.keys(fromTable));
|
|
473
|
+
const toColumns = new Set(Object.keys(toTable));
|
|
474
|
+
const drop_columns = fromColumns.difference(toColumns);
|
|
475
|
+
const add_columns = Object.fromEntries(toColumns
|
|
476
|
+
.difference(fromColumns)
|
|
477
|
+
.keys()
|
|
478
|
+
.map(colName => [colName, toTable.columns[colName]]));
|
|
479
|
+
const alter_columns = Object.fromEntries(toColumns
|
|
480
|
+
.intersection(fromColumns)
|
|
481
|
+
.keys()
|
|
482
|
+
.map(name => {
|
|
483
|
+
const fromCol = fromTable.columns[name], toCol = toTable.columns[name];
|
|
484
|
+
const alter = { ops: [] };
|
|
485
|
+
if ('default' in fromCol && !('default' in toCol))
|
|
486
|
+
alter.ops.push('drop_default');
|
|
487
|
+
else if (fromCol.default !== toCol.default)
|
|
488
|
+
alter.default = toCol.default;
|
|
489
|
+
if (fromCol.type != toCol.type)
|
|
490
|
+
alter.type = toCol.type;
|
|
491
|
+
if (fromCol.required != toCol.required)
|
|
492
|
+
alter.ops.push(toCol.required ? 'set_required' : 'drop_required');
|
|
493
|
+
return [name, alter];
|
|
494
|
+
}));
|
|
495
|
+
const fromConstraints = new Set(Object.keys(fromTable.constraints || {}));
|
|
496
|
+
const toConstraints = new Set(Object.keys(toTable.constraints || {}));
|
|
497
|
+
const drop_constraints = fromConstraints.difference(toConstraints);
|
|
498
|
+
const add_constraints = Object.fromEntries(toConstraints
|
|
499
|
+
.difference(fromConstraints)
|
|
500
|
+
.keys()
|
|
501
|
+
.map(constName => [constName, toTable.constraints[constName]]));
|
|
502
|
+
alter_tables[name] = {
|
|
503
|
+
add_columns,
|
|
504
|
+
drop_columns: Array.from(drop_columns),
|
|
505
|
+
alter_columns,
|
|
506
|
+
add_constraints,
|
|
507
|
+
drop_constraints: Array.from(drop_constraints),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
delta: true,
|
|
512
|
+
add_tables,
|
|
513
|
+
drop_tables: Array.from(fromTables.difference(toTables)),
|
|
514
|
+
alter_tables,
|
|
515
|
+
drop_indexes: Array.from(fromIndexes.difference(toIndexes)),
|
|
516
|
+
add_indexes: Array.from(toIndexes.difference(fromIndexes)),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
export function collapseDeltas(deltas) {
|
|
520
|
+
const add_tables = {}, drop_tables = [], alter_tables = {}, add_indexes = [], drop_indexes = [];
|
|
521
|
+
for (const delta of deltas) {
|
|
522
|
+
validateDelta(delta);
|
|
523
|
+
for (const [name, table] of Object.entries(delta.alter_tables)) {
|
|
524
|
+
if (name in add_tables) {
|
|
525
|
+
applyTableDeltaToSchema(add_tables[name], table);
|
|
526
|
+
}
|
|
527
|
+
else if (name in alter_tables) {
|
|
528
|
+
const existing = alter_tables[name];
|
|
529
|
+
for (const [colName, column] of Object.entries(table.add_columns)) {
|
|
530
|
+
existing.add_columns[colName] = column;
|
|
531
|
+
}
|
|
532
|
+
for (const colName of table.drop_columns) {
|
|
533
|
+
if (colName in existing.add_columns)
|
|
534
|
+
delete existing.add_columns[colName];
|
|
535
|
+
else
|
|
536
|
+
existing.drop_columns.push(colName);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else
|
|
540
|
+
alter_tables[name] = table;
|
|
541
|
+
}
|
|
542
|
+
for (const table of delta.drop_tables) {
|
|
543
|
+
if (table in add_tables)
|
|
544
|
+
delete add_tables[table];
|
|
545
|
+
else
|
|
546
|
+
drop_tables.push(table);
|
|
547
|
+
}
|
|
548
|
+
for (const [name, table] of Object.entries(delta.add_tables)) {
|
|
549
|
+
if (drop_tables.includes(name))
|
|
550
|
+
throw `Can't add and drop table "${name}" in the same change`;
|
|
551
|
+
if (name in alter_tables)
|
|
552
|
+
throw `Can't add and modify table "${name}" in the same change`;
|
|
553
|
+
add_tables[name] = table;
|
|
554
|
+
}
|
|
555
|
+
for (const index of delta.add_indexes) {
|
|
556
|
+
if (drop_indexes.includes(index))
|
|
557
|
+
throw `Can't add and drop index "${index}" in the same change`;
|
|
558
|
+
add_indexes.push(index);
|
|
559
|
+
}
|
|
560
|
+
for (const index of delta.drop_indexes) {
|
|
561
|
+
if (add_indexes.includes(index))
|
|
562
|
+
throw `Can't add and drop index "${index}" in the same change`;
|
|
563
|
+
drop_indexes.push(index);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return { delta: true, add_tables, drop_tables, alter_tables, add_indexes, drop_indexes };
|
|
567
|
+
}
|
|
568
|
+
export function deltaIsEmpty(delta) {
|
|
569
|
+
return (!Object.keys(delta.add_tables).length &&
|
|
570
|
+
!delta.drop_tables.length &&
|
|
571
|
+
!Object.keys(delta.alter_tables).length &&
|
|
572
|
+
!delta.add_indexes.length &&
|
|
573
|
+
!delta.drop_indexes.length);
|
|
574
|
+
}
|
|
575
|
+
const deltaColors = {
|
|
576
|
+
'+': 'green',
|
|
577
|
+
'-': 'red',
|
|
578
|
+
'*': 'white',
|
|
579
|
+
};
|
|
580
|
+
export function* displayDelta(delta) {
|
|
581
|
+
const tables = [
|
|
582
|
+
...Object.keys(delta.add_tables).map(name => ({ op: '+', name })),
|
|
583
|
+
...Object.entries(delta.alter_tables).map(([name, changes]) => ({ op: '*', name, changes })),
|
|
584
|
+
...delta.drop_tables.map(name => ({ op: '-', name })),
|
|
585
|
+
];
|
|
586
|
+
tables.sort((a, b) => a.name.localeCompare(b.name));
|
|
587
|
+
for (const table of tables) {
|
|
588
|
+
yield styleText(deltaColors[table.op], `${table.op} table ${table.name}`);
|
|
589
|
+
if (table.op != '*')
|
|
590
|
+
continue;
|
|
591
|
+
const columns = [
|
|
592
|
+
...Object.keys(table.changes.add_columns).map(name => ({ op: '+', name })),
|
|
593
|
+
...table.changes.drop_columns.map(name => ({ op: '-', name })),
|
|
594
|
+
...Object.entries(table.changes.alter_columns).map(([name, changes]) => ({ op: '*', name, ...changes })),
|
|
595
|
+
];
|
|
596
|
+
columns.sort((a, b) => a.name.localeCompare(b.name));
|
|
597
|
+
for (const column of columns) {
|
|
598
|
+
const columnChanges = column.op == '*'
|
|
599
|
+
? [...(column.ops ?? []), 'default' in column && 'set_default', 'type' in column && 'set_type']
|
|
600
|
+
.filter((e) => !!e)
|
|
601
|
+
.map(e => e.replaceAll('_', ' '))
|
|
602
|
+
.join(', ')
|
|
603
|
+
: null;
|
|
604
|
+
yield '\t' +
|
|
605
|
+
styleText(deltaColors[column.op], `${column.op} column ${column.name}${column.op != '*' ? '' : ': ' + columnChanges}`);
|
|
606
|
+
}
|
|
607
|
+
const constraints = [
|
|
608
|
+
...Object.keys(table.changes.add_constraints).map(name => ({ op: '+', name })),
|
|
609
|
+
...table.changes.drop_constraints.map(name => ({ op: '-', name })),
|
|
610
|
+
];
|
|
611
|
+
for (const con of constraints) {
|
|
612
|
+
yield '\t' + styleText(deltaColors[con.op], `${con.op} constraint ${con.name}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const indexes = [
|
|
616
|
+
...delta.add_indexes.map(raw => ({ op: '+', ...parseIndex(raw) })),
|
|
617
|
+
...delta.drop_indexes.map(raw => ({ op: '-', ...parseIndex(raw) })),
|
|
618
|
+
];
|
|
619
|
+
indexes.sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column));
|
|
620
|
+
for (const index of indexes) {
|
|
621
|
+
yield styleText(deltaColors[index.op], `${index.op} index on ${index.table}.${index.column}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
function columnFromSchema(column, allowPK) {
|
|
625
|
+
return function _addColumn(col) {
|
|
626
|
+
if (column.primary && allowPK)
|
|
627
|
+
col = col.primaryKey();
|
|
628
|
+
if (column.unique)
|
|
629
|
+
col = col.unique();
|
|
630
|
+
if (column.required)
|
|
631
|
+
col = col.notNull();
|
|
632
|
+
else if (column.unique)
|
|
633
|
+
col = col.nullsNotDistinct();
|
|
634
|
+
if (column.references)
|
|
635
|
+
col = col.references(column.references);
|
|
636
|
+
if (column.onDelete)
|
|
637
|
+
col = col.onDelete(column.onDelete);
|
|
638
|
+
if ('default' in column)
|
|
639
|
+
col = col.defaultTo(sql.raw(column.default));
|
|
640
|
+
if (column.check)
|
|
641
|
+
col = col.check(sql.raw(column.check));
|
|
642
|
+
return col;
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
export async function applyDelta(delta, forceAbort = false) {
|
|
646
|
+
const tx = await database.startTransaction().execute();
|
|
647
|
+
try {
|
|
648
|
+
for (const [tableName, table] of Object.entries(delta.add_tables)) {
|
|
649
|
+
io.start('Adding table ' + tableName);
|
|
650
|
+
let query = tx.schema.createTable(tableName);
|
|
651
|
+
const columns = Object.entries(table.columns);
|
|
652
|
+
const pkColumns = columns.filter(([, column]) => column.primary).map(([name, column]) => ({ name, ...column }));
|
|
653
|
+
const needsSpecialConstraint = pkColumns.length > 1 || pkColumns.some(col => !col.required);
|
|
654
|
+
for (const [colName, column] of columns) {
|
|
655
|
+
query = query.addColumn(colName, sql.raw(column.type), columnFromSchema(column, !needsSpecialConstraint));
|
|
656
|
+
}
|
|
657
|
+
if (needsSpecialConstraint) {
|
|
658
|
+
query = query.addPrimaryKeyConstraint('PK_' + tableName.replaceAll('.', '_'), pkColumns.map(col => col.name));
|
|
659
|
+
}
|
|
660
|
+
await query.execute();
|
|
661
|
+
io.done();
|
|
662
|
+
}
|
|
663
|
+
for (const tableName of delta.drop_tables) {
|
|
664
|
+
io.start('Dropping table ' + tableName);
|
|
665
|
+
await tx.schema.dropTable(tableName).execute();
|
|
666
|
+
io.done();
|
|
667
|
+
}
|
|
668
|
+
for (const [tableName, tableDelta] of Object.entries(delta.alter_tables)) {
|
|
669
|
+
io.start(`Modifying table ${tableName}`);
|
|
670
|
+
const query = tx.schema.alterTable(tableName);
|
|
671
|
+
for (const constraint of tableDelta.drop_constraints) {
|
|
672
|
+
await query.dropConstraint(constraint).execute();
|
|
673
|
+
}
|
|
674
|
+
for (const colName of tableDelta.drop_columns) {
|
|
675
|
+
await query.dropColumn(colName).execute();
|
|
676
|
+
}
|
|
677
|
+
for (const [colName, column] of Object.entries(tableDelta.add_columns)) {
|
|
678
|
+
await query.addColumn(colName, sql.raw(column.type), columnFromSchema(column, false)).execute();
|
|
679
|
+
}
|
|
680
|
+
for (const [colName, column] of Object.entries(tableDelta.alter_columns)) {
|
|
681
|
+
if (column.default)
|
|
682
|
+
await query.alterColumn(colName, col => col.setDefault(sql.raw(column.default))).execute();
|
|
683
|
+
if (column.type)
|
|
684
|
+
await query.alterColumn(colName, col => col.setDataType(sql.raw(column.type))).execute();
|
|
685
|
+
for (const op of column.ops ?? []) {
|
|
686
|
+
switch (op) {
|
|
687
|
+
case 'drop_default':
|
|
688
|
+
if (column.default)
|
|
689
|
+
throw 'Cannot set and drop default at the same time';
|
|
690
|
+
await query.alterColumn(colName, col => col.dropDefault()).execute();
|
|
691
|
+
break;
|
|
692
|
+
case 'set_required':
|
|
693
|
+
await query.alterColumn(colName, col => col.setNotNull()).execute();
|
|
694
|
+
break;
|
|
695
|
+
case 'drop_required':
|
|
696
|
+
await query.alterColumn(colName, col => col.dropNotNull()).execute();
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
for (const [name, con] of Object.entries(tableDelta.add_constraints)) {
|
|
702
|
+
switch (con.type) {
|
|
703
|
+
case 'unique':
|
|
704
|
+
await query.addUniqueConstraint(name, con.on, b => (con.nulls_not_distinct ? b.nullsNotDistinct() : b)).execute();
|
|
705
|
+
break;
|
|
706
|
+
case 'check':
|
|
707
|
+
await query.addCheckConstraint(name, sql.raw(con.check)).execute();
|
|
708
|
+
break;
|
|
709
|
+
case 'foreign_key':
|
|
710
|
+
await query.addForeignKeyConstraint(name, con.on, con.target, con.references, b => b).execute();
|
|
711
|
+
break;
|
|
712
|
+
case 'primary_key':
|
|
713
|
+
await query.addPrimaryKeyConstraint(name, con.on).execute();
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
io.done();
|
|
718
|
+
}
|
|
719
|
+
if (forceAbort)
|
|
720
|
+
throw 'Rolling back due to --abort';
|
|
721
|
+
io.start('Committing');
|
|
722
|
+
await tx.commit().execute();
|
|
723
|
+
io.done();
|
|
724
|
+
}
|
|
725
|
+
catch (e) {
|
|
726
|
+
await tx.rollback().execute();
|
|
727
|
+
if (e instanceof SuppressedError)
|
|
728
|
+
io.error(e.suppressed);
|
|
729
|
+
throw e;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
372
732
|
/**
|
|
373
733
|
* Checks that a table has the expected column types, nullability, and default values.
|
|
374
734
|
*/
|
|
375
735
|
export async function checkTableTypes(tableName, types, opt) {
|
|
376
|
-
io.start(`Checking
|
|
736
|
+
io.start(`Checking table ${tableName}`);
|
|
377
737
|
const dbTables = opt._metadata || (await database.introspection.getTables());
|
|
378
738
|
const table = dbTables.find(t => (t.schema == 'public' ? t.name : `${t.schema}.${t.name}`) === tableName);
|
|
379
739
|
if (!table)
|
|
380
740
|
throw 'missing.';
|
|
381
|
-
io.done();
|
|
382
741
|
const columns = Object.fromEntries(table.columns.map(c => [c.name, c]));
|
|
383
|
-
|
|
384
|
-
|
|
742
|
+
const _types = Object.entries(types.columns);
|
|
743
|
+
for (const [i, [key, { type, required = false, default: _default }]] of _types.entries()) {
|
|
744
|
+
io.progress(i, _types.length, key);
|
|
385
745
|
const col = columns[key];
|
|
386
|
-
|
|
387
|
-
|
|
746
|
+
const actualType = type in schemaToIntrospected ? schemaToIntrospected[type] : type;
|
|
747
|
+
const hasDefault = _default !== undefined;
|
|
388
748
|
try {
|
|
389
|
-
if (col
|
|
390
|
-
throw
|
|
749
|
+
if (!col)
|
|
750
|
+
throw 'missing.';
|
|
751
|
+
if (col.dataType != actualType)
|
|
752
|
+
throw `incorrect type "${col.dataType}", expected ${actualType} (${type})`;
|
|
391
753
|
if (col.isNullable != !required)
|
|
392
754
|
throw required ? 'nullable' : 'not nullable';
|
|
393
755
|
if (col.hasDefaultValue != hasDefault)
|
|
394
756
|
throw hasDefault ? 'missing default' : 'has default';
|
|
395
|
-
io.done();
|
|
396
757
|
}
|
|
397
758
|
catch (e) {
|
|
398
759
|
if (opt.strict)
|
|
399
|
-
throw e
|
|
400
|
-
io.warn(e);
|
|
760
|
+
throw `${tableName}.${key}: ${e}`;
|
|
761
|
+
io.warn(`${tableName}.${key}: ${e}`);
|
|
401
762
|
}
|
|
402
763
|
delete columns[key];
|
|
403
764
|
}
|
|
@@ -414,43 +775,6 @@ export async function checkTableTypes(tableName, types, opt) {
|
|
|
414
775
|
else
|
|
415
776
|
io.warn(unchecked);
|
|
416
777
|
}
|
|
417
|
-
export async function check(opt) {
|
|
418
|
-
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
419
|
-
try {
|
|
420
|
-
await io.run('Checking for sudo', 'which sudo');
|
|
421
|
-
await io.run('Checking for psql', 'which psql');
|
|
422
|
-
await _sql(`SELECT 1 FROM pg_database WHERE datname = 'axium'`, 'Checking for database').then(throwUnlessRows);
|
|
423
|
-
await _sql(`SELECT 1 FROM pg_roles WHERE rolname = 'axium'`, 'Checking for user').then(throwUnlessRows);
|
|
424
|
-
io.start('Connecting to database');
|
|
425
|
-
const _ = __addDisposableResource(env_2, connect(), true);
|
|
426
|
-
io.done();
|
|
427
|
-
io.start('Getting table metadata');
|
|
428
|
-
opt._metadata = await database.introspection.getTables();
|
|
429
|
-
const tables = Object.fromEntries(opt._metadata.map(t => [t.name, t]));
|
|
430
|
-
io.done();
|
|
431
|
-
for (const table of Object.keys(expectedTypes)) {
|
|
432
|
-
await checkTableTypes(table, expectedTypes[table], opt);
|
|
433
|
-
delete tables[table];
|
|
434
|
-
}
|
|
435
|
-
io.start('Checking for extra tables');
|
|
436
|
-
const unchecked = Object.keys(tables).join(', ');
|
|
437
|
-
if (!unchecked.length)
|
|
438
|
-
io.done();
|
|
439
|
-
else if (opt.strict)
|
|
440
|
-
throw unchecked;
|
|
441
|
-
else
|
|
442
|
-
io.warn(unchecked);
|
|
443
|
-
}
|
|
444
|
-
catch (e_2) {
|
|
445
|
-
env_2.error = e_2;
|
|
446
|
-
env_2.hasError = true;
|
|
447
|
-
}
|
|
448
|
-
finally {
|
|
449
|
-
const result_2 = __disposeResources(env_2);
|
|
450
|
-
if (result_2)
|
|
451
|
-
await result_2;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
778
|
export async function clean(opt) {
|
|
455
779
|
const now = new Date();
|
|
456
780
|
io.start('Removing expired sessions');
|
|
@@ -464,55 +788,6 @@ export async function clean(opt) {
|
|
|
464
788
|
await plugin._hooks?.clean(opt);
|
|
465
789
|
}
|
|
466
790
|
}
|
|
467
|
-
/**
|
|
468
|
-
* Completely remove Axium from the database.
|
|
469
|
-
*/
|
|
470
|
-
export async function uninstall(opt) {
|
|
471
|
-
for (const plugin of plugins.values()) {
|
|
472
|
-
if (!plugin._hooks?.remove)
|
|
473
|
-
continue;
|
|
474
|
-
io.log(styleText('whiteBright', 'Running plugin: '), plugin.name);
|
|
475
|
-
await plugin._hooks?.remove(opt);
|
|
476
|
-
}
|
|
477
|
-
await _sql('DROP DATABASE axium', 'Dropping database');
|
|
478
|
-
await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
|
|
479
|
-
await _sql('DROP USER axium', 'Dropping user');
|
|
480
|
-
await getHBA(opt)
|
|
481
|
-
.then(([content, writeBack]) => {
|
|
482
|
-
io.start('Checking for Axium HBA configuration');
|
|
483
|
-
if (!content.includes(pgHba))
|
|
484
|
-
throw 'missing.';
|
|
485
|
-
io.done();
|
|
486
|
-
io.start('Removing Axium HBA configuration');
|
|
487
|
-
const newContent = content.replace(pgHba, '');
|
|
488
|
-
io.done();
|
|
489
|
-
writeBack(newContent);
|
|
490
|
-
})
|
|
491
|
-
.catch(io.warn);
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Removes all data from tables.
|
|
495
|
-
*/
|
|
496
|
-
export async function wipe(opt) {
|
|
497
|
-
for (const plugin of plugins.values()) {
|
|
498
|
-
if (!plugin._hooks?.db_wipe)
|
|
499
|
-
continue;
|
|
500
|
-
io.log(styleText('whiteBright', 'Running plugin: '), plugin.name);
|
|
501
|
-
await plugin._hooks?.db_wipe(opt);
|
|
502
|
-
}
|
|
503
|
-
for (const table of ['users', 'passkeys', 'sessions', 'verifications']) {
|
|
504
|
-
io.start(`Wiping ${table}`);
|
|
505
|
-
await database.deleteFrom(table).execute();
|
|
506
|
-
io.done();
|
|
507
|
-
}
|
|
508
|
-
for (const table of await database.introspection.getTables()) {
|
|
509
|
-
if (!table.name.startsWith('acl.'))
|
|
510
|
-
continue;
|
|
511
|
-
const name = table.name;
|
|
512
|
-
io.debug(`Wiping ${name}`);
|
|
513
|
-
await database.deleteFrom(name).execute();
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
791
|
export async function rotatePassword() {
|
|
517
792
|
io.start('Generating new password');
|
|
518
793
|
const password = randomBytes(32).toString('base64');
|