tempest-express-sdk 0.26.0 → 0.28.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/README.md +1 -1
- package/dist/{chunk-GLZYNX63.js → chunk-3NS5KVHT.js} +3 -3
- package/dist/{chunk-GLZYNX63.js.map → chunk-3NS5KVHT.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +837 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +301 -4
- package/dist/index.d.ts +301 -4
- package/dist/index.js +832 -16
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z, looseBoolean, toDict, PasswordUtils } from './chunk-
|
|
2
|
-
export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, centsField, corsSettingsShape, databaseSettingsShape, looseBoolean as envBoolean, hexColorField, latitudeField, loadSettings, longitudeField, looseBoolean, nonEmptyStrField, nonNegativeFloatField, nonNegativeIntField, percentField, portField, positiveFloatField, positiveIntField, priceField, ratingField, ratioField, serverSettingsShape, slugField, toDict, z } from './chunk-
|
|
1
|
+
import { z, looseBoolean, toDict, PasswordUtils } from './chunk-3NS5KVHT.js';
|
|
2
|
+
export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, centsField, corsSettingsShape, databaseSettingsShape, looseBoolean as envBoolean, hexColorField, latitudeField, loadSettings, longitudeField, looseBoolean, nonEmptyStrField, nonNegativeFloatField, nonNegativeIntField, percentField, portField, positiveFloatField, positiveIntField, priceField, ratingField, ratioField, serverSettingsShape, slugField, toDict, z } from './chunk-3NS5KVHT.js';
|
|
3
3
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
4
|
import { Model, column, sql, BaseRepository, RecordNotFound, detectDialect, columnsOf, NodeSqliteDriver, AsyncEngine, or, and } from 'tempest-db-js';
|
|
5
5
|
export { AsyncEngine, AsyncResult, AsyncSession, BaseRepository, Column, DeleteBuilder, InsertBuilder, Model, NoResultError, NodeSqliteDriver, PostgresDialect, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncSession, UpdateBuilder, and, belongsTo, column, columnsOf, createEngine, createSyncEngine, del, detectDialect, getDialect, hasMany, insert, join, loadRelations, not, or, parseDatabaseUrl, select, sql, update } from 'tempest-db-js';
|
|
@@ -9334,6 +9334,23 @@ function partitionTotal(partition) {
|
|
|
9334
9334
|
return partition.segments.reduce((total, segment) => total + segment.value, 0);
|
|
9335
9335
|
}
|
|
9336
9336
|
|
|
9337
|
+
// src/admin/inlines.ts
|
|
9338
|
+
function adminInline(options) {
|
|
9339
|
+
const slug = options.model.tablename;
|
|
9340
|
+
if (typeof slug !== "string" || slug === "") {
|
|
9341
|
+
throw new Error("adminInline requires a concrete model with a tablename");
|
|
9342
|
+
}
|
|
9343
|
+
return {
|
|
9344
|
+
model: options.model,
|
|
9345
|
+
slug,
|
|
9346
|
+
fkField: options.fkField,
|
|
9347
|
+
listDisplay: options.listDisplay === void 0 ? null : [...options.listDisplay],
|
|
9348
|
+
label: options.label ?? null,
|
|
9349
|
+
editable: options.editable ?? false,
|
|
9350
|
+
canDelete: options.canDelete ?? false
|
|
9351
|
+
};
|
|
9352
|
+
}
|
|
9353
|
+
|
|
9337
9354
|
// src/admin/lenses.ts
|
|
9338
9355
|
function slugify(name) {
|
|
9339
9356
|
return name.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "lens";
|
|
@@ -9371,6 +9388,85 @@ function adminAction(options, handler) {
|
|
|
9371
9388
|
};
|
|
9372
9389
|
}
|
|
9373
9390
|
|
|
9391
|
+
// src/admin/multipart.ts
|
|
9392
|
+
var BUSBOY_HINT = "The admin's upload and CSV-import screens need the optional peer `busboy`. Install it with: npm install busboy";
|
|
9393
|
+
async function loadBusboy() {
|
|
9394
|
+
try {
|
|
9395
|
+
const module = await import('busboy');
|
|
9396
|
+
return module.default ?? module;
|
|
9397
|
+
} catch {
|
|
9398
|
+
throw new Error(BUSBOY_HINT);
|
|
9399
|
+
}
|
|
9400
|
+
}
|
|
9401
|
+
var MultipartLimitError = class extends Error {
|
|
9402
|
+
/**
|
|
9403
|
+
* @param message - The operator-facing explanation.
|
|
9404
|
+
*/
|
|
9405
|
+
constructor(message) {
|
|
9406
|
+
super(message);
|
|
9407
|
+
this.name = "MultipartLimitError";
|
|
9408
|
+
}
|
|
9409
|
+
};
|
|
9410
|
+
async function parseMultipart(req, options = {}) {
|
|
9411
|
+
const maxFileBytes = options.maxFileBytes ?? 10 * 1024 * 1024;
|
|
9412
|
+
const maxFiles = options.maxFiles ?? 10;
|
|
9413
|
+
const busboy = await loadBusboy();
|
|
9414
|
+
return await new Promise((resolve, reject) => {
|
|
9415
|
+
const fields = {};
|
|
9416
|
+
const files = [];
|
|
9417
|
+
let settled = false;
|
|
9418
|
+
const fail = (error) => {
|
|
9419
|
+
if (settled) return;
|
|
9420
|
+
settled = true;
|
|
9421
|
+
reject(error);
|
|
9422
|
+
};
|
|
9423
|
+
const parser = busboy({
|
|
9424
|
+
headers: req.headers,
|
|
9425
|
+
limits: { fileSize: maxFileBytes, files: maxFiles }
|
|
9426
|
+
});
|
|
9427
|
+
parser.on("field", ((name, value) => {
|
|
9428
|
+
fields[name] = value;
|
|
9429
|
+
}));
|
|
9430
|
+
parser.on("file", ((name, stream, info) => {
|
|
9431
|
+
const chunks = [];
|
|
9432
|
+
stream.on("data", ((chunk) => chunks.push(chunk)));
|
|
9433
|
+
stream.on("limit", () => {
|
|
9434
|
+
fail(
|
|
9435
|
+
new MultipartLimitError(
|
|
9436
|
+
`The file is larger than the ${Math.floor(maxFileBytes / 1024 / 1024)} MB limit.`
|
|
9437
|
+
)
|
|
9438
|
+
);
|
|
9439
|
+
});
|
|
9440
|
+
stream.on("end", () => {
|
|
9441
|
+
const data = Buffer.concat(chunks);
|
|
9442
|
+
const filename = (info.filename ?? "").split(/[/\\]/).pop() ?? "";
|
|
9443
|
+
if (filename === "" || data.length === 0) return;
|
|
9444
|
+
files.push({
|
|
9445
|
+
field: name,
|
|
9446
|
+
filename,
|
|
9447
|
+
contentType: info.mimeType ?? "application/octet-stream",
|
|
9448
|
+
data
|
|
9449
|
+
});
|
|
9450
|
+
});
|
|
9451
|
+
}));
|
|
9452
|
+
parser.on("filesLimit", () => {
|
|
9453
|
+
fail(new MultipartLimitError(`At most ${maxFiles} files can be uploaded at once.`));
|
|
9454
|
+
});
|
|
9455
|
+
parser.on("error", ((error) => {
|
|
9456
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
9457
|
+
}));
|
|
9458
|
+
parser.on("close", () => {
|
|
9459
|
+
if (settled) return;
|
|
9460
|
+
settled = true;
|
|
9461
|
+
resolve({ fields, files });
|
|
9462
|
+
});
|
|
9463
|
+
req.pipe(parser);
|
|
9464
|
+
});
|
|
9465
|
+
}
|
|
9466
|
+
function isMultipart(req) {
|
|
9467
|
+
return (req.header("content-type") ?? "").toLowerCase().startsWith("multipart/form-data");
|
|
9468
|
+
}
|
|
9469
|
+
|
|
9374
9470
|
// src/admin/columns.ts
|
|
9375
9471
|
function humanizeField(name) {
|
|
9376
9472
|
return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().split(/\s+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
@@ -9464,6 +9560,8 @@ var NEVER_EDITABLE = [
|
|
|
9464
9560
|
"id",
|
|
9465
9561
|
"createdAt",
|
|
9466
9562
|
"updatedAt",
|
|
9563
|
+
"createdBy",
|
|
9564
|
+
"updatedBy",
|
|
9467
9565
|
"hashedPassword"
|
|
9468
9566
|
];
|
|
9469
9567
|
var NEVER_LISTED = ["hashedPassword"];
|
|
@@ -9500,6 +9598,16 @@ var AdminModel = class {
|
|
|
9500
9598
|
auditModel;
|
|
9501
9599
|
/** Saved list-view presets, in declaration order. */
|
|
9502
9600
|
lenses;
|
|
9601
|
+
/** Columns rendered as file inputs. */
|
|
9602
|
+
uploadFields;
|
|
9603
|
+
/** Backend persisting uploaded files, or `null`. */
|
|
9604
|
+
uploadStorage;
|
|
9605
|
+
/** Whether the CSV import page is exposed. */
|
|
9606
|
+
canImport;
|
|
9607
|
+
/** Foreign-key columns rendered as a typed search box. */
|
|
9608
|
+
autocompleteFields;
|
|
9609
|
+
/** Related child models listed on the detail view. */
|
|
9610
|
+
inlines;
|
|
9503
9611
|
actions = /* @__PURE__ */ new Map();
|
|
9504
9612
|
slugOverride;
|
|
9505
9613
|
listDisplayOverride;
|
|
@@ -9527,6 +9635,16 @@ var AdminModel = class {
|
|
|
9527
9635
|
this.canDelete = options.canDelete ?? true;
|
|
9528
9636
|
this.auditModel = options.auditModel ?? null;
|
|
9529
9637
|
this.lenses = [...options.lenses ?? []];
|
|
9638
|
+
this.uploadFields = [...options.uploadFields ?? []];
|
|
9639
|
+
this.uploadStorage = options.uploadStorage ?? null;
|
|
9640
|
+
this.canImport = options.canImport ?? false;
|
|
9641
|
+
this.autocompleteFields = [...options.autocompleteFields ?? []];
|
|
9642
|
+
this.inlines = [...options.inlines ?? []];
|
|
9643
|
+
if (this.uploadFields.length > 0 && this.uploadStorage === null) {
|
|
9644
|
+
throw new Error(
|
|
9645
|
+
`AdminModel(${this.model.tablename}).uploadFields requires an uploadStorage (e.g. LocalUploadStorage / S3UploadStorage) \u2014 without one there is nowhere to write the file.`
|
|
9646
|
+
);
|
|
9647
|
+
}
|
|
9530
9648
|
for (const action of options.actions ?? []) {
|
|
9531
9649
|
if (this.actions.has(action.name)) {
|
|
9532
9650
|
throw new Error(
|
|
@@ -9541,7 +9659,9 @@ var AdminModel = class {
|
|
|
9541
9659
|
["listFilter", this.listFilter],
|
|
9542
9660
|
["searchFields", this.searchFields],
|
|
9543
9661
|
["readonlyFields", this.readonlyFields],
|
|
9544
|
-
["identityField", [this.identityField]]
|
|
9662
|
+
["identityField", [this.identityField]],
|
|
9663
|
+
["uploadFields", this.uploadFields],
|
|
9664
|
+
["autocompleteFields", this.autocompleteFields]
|
|
9545
9665
|
]) {
|
|
9546
9666
|
for (const name of names) {
|
|
9547
9667
|
if (!known.has(name)) {
|
|
@@ -9723,11 +9843,15 @@ function buildFormFields(admin, options = {}) {
|
|
|
9723
9843
|
const values = options.values ?? {};
|
|
9724
9844
|
const errors = options.errors ?? {};
|
|
9725
9845
|
const foreignKeys = options.foreignKeyOptions ?? {};
|
|
9846
|
+
const autocompleteUrls = options.autocompleteUrls ?? {};
|
|
9847
|
+
const autocompleteLabels = options.autocompleteLabels ?? {};
|
|
9848
|
+
const uploads = new Set(admin.uploadFields);
|
|
9726
9849
|
return admin.editableFieldNames().flatMap((name) => {
|
|
9727
9850
|
const column6 = columns[name];
|
|
9728
9851
|
if (column6 === void 0) return [];
|
|
9729
9852
|
const related = foreignKeys[name];
|
|
9730
|
-
const
|
|
9853
|
+
const autocompleteUrl = autocompleteUrls[name];
|
|
9854
|
+
const spec = uploads.has(name) ? { widget: "file", step: null, options: [] } : autocompleteUrl !== void 0 ? { widget: "autocomplete", step: null, options: [] } : related === void 0 ? widgetForColumn(column6) : { widget: "select", step: null, options: related };
|
|
9731
9855
|
const raw = name in values ? values[name] : literalDefault(column6);
|
|
9732
9856
|
return [
|
|
9733
9857
|
{
|
|
@@ -9739,7 +9863,9 @@ function buildFormFields(admin, options = {}) {
|
|
|
9739
9863
|
checked: spec.widget === "checkbox" && toBoolean(raw),
|
|
9740
9864
|
step: spec.step,
|
|
9741
9865
|
options: spec.options,
|
|
9742
|
-
error: errors[name] ?? null
|
|
9866
|
+
error: errors[name] ?? null,
|
|
9867
|
+
autocompleteUrl: autocompleteUrl ?? null,
|
|
9868
|
+
displayLabel: autocompleteLabels[name] ?? ""
|
|
9743
9869
|
}
|
|
9744
9870
|
];
|
|
9745
9871
|
});
|
|
@@ -9787,13 +9913,17 @@ function coerceValue(column6, widget, raw) {
|
|
|
9787
9913
|
return raw;
|
|
9788
9914
|
}
|
|
9789
9915
|
}
|
|
9790
|
-
function parseFormBody(admin, body) {
|
|
9916
|
+
function parseFormBody(admin, body, options = {}) {
|
|
9791
9917
|
const columns = adminColumns(admin.model);
|
|
9792
9918
|
const data = {};
|
|
9793
9919
|
const errors = {};
|
|
9920
|
+
const uploads = options.uploadsAsText === true ? /* @__PURE__ */ new Set() : new Set(admin.uploadFields);
|
|
9921
|
+
const only = options.only === void 0 ? null : new Set(options.only);
|
|
9794
9922
|
for (const name of admin.editableFieldNames()) {
|
|
9923
|
+
if (only !== null && !only.has(name)) continue;
|
|
9795
9924
|
const column6 = columns[name];
|
|
9796
9925
|
if (column6 === void 0) continue;
|
|
9926
|
+
if (uploads.has(name)) continue;
|
|
9797
9927
|
const { widget } = widgetForColumn(column6);
|
|
9798
9928
|
if (widget === "checkbox") {
|
|
9799
9929
|
data[name] = toBoolean(body[name]);
|
|
@@ -12079,6 +12209,7 @@ function renderListPage(context, view) {
|
|
|
12079
12209
|
</form>
|
|
12080
12210
|
<div class="tempest-admin-list__actions">
|
|
12081
12211
|
${view.newUrl !== null ? `<a class="tempest-admin-list__new" href="${escapeHtml(view.newUrl)}">+ New</a>` : ""}
|
|
12212
|
+
${view.importUrl !== null ? `<a href="${escapeHtml(view.importUrl)}">Import CSV</a>` : ""}
|
|
12082
12213
|
<a href="${escapeHtml(view.exportCsvUrl)}">Export CSV</a>
|
|
12083
12214
|
<a href="${escapeHtml(view.exportJsonUrl)}">Export JSON</a>
|
|
12084
12215
|
</div>
|
|
@@ -12123,10 +12254,97 @@ function renderDetailPage(context, view) {
|
|
|
12123
12254
|
</div>
|
|
12124
12255
|
</header>
|
|
12125
12256
|
<dl class="tempest-admin-detail__fields">${fields}</dl>
|
|
12257
|
+
${view.inlines.map((inline) => renderInline(inline, context.session?.csrfToken ?? "", view.inlineError)).join("")}
|
|
12126
12258
|
${auditPanel}
|
|
12127
12259
|
</section>`;
|
|
12128
12260
|
return renderLayout(context, `${view.title} \xB7 ${view.identity}`, body);
|
|
12129
12261
|
}
|
|
12262
|
+
function renderInlineCell(field) {
|
|
12263
|
+
const required = field.required ? " required" : "";
|
|
12264
|
+
const name = escapeHtml(field.name);
|
|
12265
|
+
const value = escapeHtml(field.value);
|
|
12266
|
+
let control;
|
|
12267
|
+
switch (field.widget) {
|
|
12268
|
+
case "checkbox":
|
|
12269
|
+
control = `<input type="checkbox" name="${name}" value="true"${field.checked ? " checked" : ""}>`;
|
|
12270
|
+
break;
|
|
12271
|
+
case "textarea":
|
|
12272
|
+
case "json":
|
|
12273
|
+
control = `<textarea name="${name}" rows="2"${required}>${value}</textarea>`;
|
|
12274
|
+
break;
|
|
12275
|
+
case "select": {
|
|
12276
|
+
const blank = field.required ? "" : '<option value="">\u2014 none \u2014</option>';
|
|
12277
|
+
const options = field.options.map(
|
|
12278
|
+
(option) => `<option value="${escapeHtml(option.value)}"${option.value === field.value ? " selected" : ""}>${escapeHtml(option.label)}</option>`
|
|
12279
|
+
).join("");
|
|
12280
|
+
control = `<select name="${name}"${required}>${blank}${options}</select>`;
|
|
12281
|
+
break;
|
|
12282
|
+
}
|
|
12283
|
+
case "number":
|
|
12284
|
+
control = `<input type="number" name="${name}" value="${value}"${field.step !== null ? ` step="${escapeHtml(field.step)}"` : ""}${required}>`;
|
|
12285
|
+
break;
|
|
12286
|
+
case "datetime":
|
|
12287
|
+
control = `<input type="datetime-local" name="${name}" value="${value}"${required}>`;
|
|
12288
|
+
break;
|
|
12289
|
+
case "date":
|
|
12290
|
+
control = `<input type="date" name="${name}" value="${value}"${required}>`;
|
|
12291
|
+
break;
|
|
12292
|
+
case "time":
|
|
12293
|
+
control = `<input type="time" name="${name}" value="${value}"${required}>`;
|
|
12294
|
+
break;
|
|
12295
|
+
default:
|
|
12296
|
+
control = `<input type="text" name="${name}" value="${value}"${required}>`;
|
|
12297
|
+
}
|
|
12298
|
+
const error = field.error !== null ? `<small class="tempest-admin-form__field-error">${escapeHtml(field.error)}</small>` : "";
|
|
12299
|
+
return `${control}${error}`;
|
|
12300
|
+
}
|
|
12301
|
+
function renderInline(inline, csrfToken, error) {
|
|
12302
|
+
const heading = `<header class="tempest-admin-inline__header">
|
|
12303
|
+
<h2>${escapeHtml(inline.label)}${inline.total > 0 ? ` <span class="tempest-admin-inline__count">(${escapeHtml(inline.total)})</span>` : ""}</h2>
|
|
12304
|
+
${inline.addUrl !== null ? `<a class="tempest-admin-btn" href="${escapeHtml(inline.addUrl)}">Add</a>` : ""}
|
|
12305
|
+
</header>`;
|
|
12306
|
+
const more = inline.truncated ? `<p class="tempest-admin-inline__more"><em>Showing the first ${escapeHtml(inline.rows.length)} of ${escapeHtml(inline.total)}.</em></p>` : "";
|
|
12307
|
+
if (!inline.editable) {
|
|
12308
|
+
if (inline.rows.length === 0) {
|
|
12309
|
+
return `<section class="tempest-admin-inline">${heading}<p><em>No related records.</em></p></section>`;
|
|
12310
|
+
}
|
|
12311
|
+
const head2 = `<tr>${inline.columns.map((column6) => `<th>${escapeHtml(column6)}</th>`).join("")}<th></th></tr>`;
|
|
12312
|
+
const body = inline.rows.map(
|
|
12313
|
+
(row) => `<tr>${row.cells.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}<td>${row.url === null ? "" : `<a href="${escapeHtml(row.url)}">View</a>`}</td></tr>`
|
|
12314
|
+
).join("");
|
|
12315
|
+
return `<section class="tempest-admin-inline">
|
|
12316
|
+
${heading}
|
|
12317
|
+
<div class="tempest-admin-inline__scroll">
|
|
12318
|
+
<table class="tempest-admin-inline__table">
|
|
12319
|
+
<thead>${head2}</thead>
|
|
12320
|
+
<tbody>${body}</tbody>
|
|
12321
|
+
</table>
|
|
12322
|
+
</div>
|
|
12323
|
+
${more}
|
|
12324
|
+
</section>`;
|
|
12325
|
+
}
|
|
12326
|
+
const head = `<tr>${inline.columns.map((column6) => `<th>${escapeHtml(column6)}</th>`).join("")}${inline.canDelete ? "<th>Delete</th>" : ""}</tr>`;
|
|
12327
|
+
const renderRow = (row, isNew) => `<tr${isNew ? ' class="tempest-admin-inline__new"' : ""}>${row.fields.map((field) => `<td>${renderInlineCell(field)}</td>`).join("")}${inline.canDelete ? `<td class="tempest-admin-inline__del">${isNew ? "" : `<input type="checkbox" name="row.${escapeHtml(row.key)}.__delete" value="true">`}</td>` : ""}</tr>`;
|
|
12328
|
+
const rows = inline.rows.map((row) => renderRow(row, false)).join("");
|
|
12329
|
+
const blank = inline.newRow === null ? "" : renderRow(inline.newRow, true);
|
|
12330
|
+
return `<section class="tempest-admin-inline">
|
|
12331
|
+
${heading}
|
|
12332
|
+
${error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(error)}</p>` : ""}
|
|
12333
|
+
<form method="post" action="${escapeHtml(inline.formAction)}" class="tempest-admin-inline__form">
|
|
12334
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(csrfToken)}">
|
|
12335
|
+
<div class="tempest-admin-inline__scroll">
|
|
12336
|
+
<table class="tempest-admin-inline__table">
|
|
12337
|
+
<thead>${head}</thead>
|
|
12338
|
+
<tbody>${rows}${blank}</tbody>
|
|
12339
|
+
</table>
|
|
12340
|
+
</div>
|
|
12341
|
+
<div class="tempest-admin-form__actions">
|
|
12342
|
+
<button type="submit">Save ${escapeHtml(inline.label)}</button>
|
|
12343
|
+
</div>
|
|
12344
|
+
</form>
|
|
12345
|
+
${more}
|
|
12346
|
+
</section>`;
|
|
12347
|
+
}
|
|
12130
12348
|
function renderAuditPanel(audit) {
|
|
12131
12349
|
const rows = audit.fields.map(
|
|
12132
12350
|
(field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
|
|
@@ -12178,6 +12396,18 @@ function renderFormField(field) {
|
|
|
12178
12396
|
control = `<label><span>${label}${field.required ? " *" : ""}</span><select name="${name}"${required}>${blank}${options}</select></label>`;
|
|
12179
12397
|
break;
|
|
12180
12398
|
}
|
|
12399
|
+
case "file":
|
|
12400
|
+
control = `<label><span>${label}${field.required ? " *" : ""}</span><input type="file" name="${name}"${required}></label>${field.value === "" ? "" : `<small class="tempest-admin-form__hint">Current: ${value} \u2014 choose a file to replace it.</small>`}`;
|
|
12401
|
+
break;
|
|
12402
|
+
case "autocomplete":
|
|
12403
|
+
control = `<label><span>${label}${field.required ? " *" : ""}</span>
|
|
12404
|
+
<div class="tempest-admin-ac" data-ac data-ac-url="${escapeHtml(field.autocompleteUrl)}">
|
|
12405
|
+
<input type="text" class="tempest-admin-ac__search" value="${escapeHtml(field.displayLabel)}" placeholder="Search\u2026" autocomplete="off" data-ac-search>
|
|
12406
|
+
<input type="hidden" name="${name}" value="${value}"${required} data-ac-value>
|
|
12407
|
+
<ul class="tempest-admin-ac__results" data-ac-results hidden></ul>
|
|
12408
|
+
</div>
|
|
12409
|
+
</label>`;
|
|
12410
|
+
break;
|
|
12181
12411
|
case "number":
|
|
12182
12412
|
control = `<label><span>${label}${field.required ? " *" : ""}</span><input type="number" name="${name}" value="${value}"${field.step !== null ? ` step="${escapeHtml(field.step)}"` : ""}${required}></label>`;
|
|
12183
12413
|
break;
|
|
@@ -12205,7 +12435,7 @@ function renderFormPage(context, view) {
|
|
|
12205
12435
|
<a href="${escapeHtml(view.backUrl)}">\u2190 Back</a>
|
|
12206
12436
|
</header>
|
|
12207
12437
|
${view.error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>` : ""}
|
|
12208
|
-
<form method="post" action="${escapeHtml(view.actionUrl)}" class="tempest-admin-form__form">
|
|
12438
|
+
<form method="post" action="${escapeHtml(view.actionUrl)}" class="tempest-admin-form__form"${view.fields.some((field) => field.widget === "file") ? ' enctype="multipart/form-data"' : ""}>
|
|
12209
12439
|
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
12210
12440
|
${view.fields.map(renderFormField).join("")}
|
|
12211
12441
|
<div class="tempest-admin-form__actions">
|
|
@@ -12213,10 +12443,98 @@ function renderFormPage(context, view) {
|
|
|
12213
12443
|
<a href="${escapeHtml(view.backUrl)}" class="tempest-admin-form__cancel">Cancel</a>
|
|
12214
12444
|
</div>
|
|
12215
12445
|
</form>
|
|
12446
|
+
${view.fields.some((field) => field.widget === "autocomplete") ? AUTOCOMPLETE_SCRIPT : ""}
|
|
12216
12447
|
</section>`;
|
|
12217
12448
|
return renderLayout(context, `${heading} \xB7 ${context.site.title}`, body);
|
|
12218
12449
|
}
|
|
12450
|
+
function renderImportPage(context, view) {
|
|
12451
|
+
if (context.session === null) throw new Error("The import page requires a session");
|
|
12452
|
+
const summary = view.created === null ? "" : `<p class="tempest-admin-import__summary">Created ${escapeHtml(view.created)} record${view.created === 1 ? "" : "s"}.</p>`;
|
|
12453
|
+
const failures = view.rowErrors.length > 0 ? `<table class="tempest-admin-import__errors"><thead><tr><th>Row</th><th>Problem</th></tr></thead><tbody>${view.rowErrors.map(
|
|
12454
|
+
(failure) => `<tr><td>${escapeHtml(failure.row)}</td><td>${escapeHtml(failure.message)}</td></tr>`
|
|
12455
|
+
).join("")}</tbody></table>` : "";
|
|
12456
|
+
const body = `<section class="tempest-admin-import">
|
|
12457
|
+
<header class="tempest-admin-detail__header">
|
|
12458
|
+
<h1>Import ${escapeHtml(view.title)}</h1>
|
|
12459
|
+
<a href="${escapeHtml(view.backUrl)}">\u2190 Back</a>
|
|
12460
|
+
</header>
|
|
12461
|
+
${view.error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>` : ""}
|
|
12462
|
+
${summary}
|
|
12463
|
+
${failures}
|
|
12464
|
+
<form method="post" action="${escapeHtml(view.actionUrl)}" class="tempest-admin-form__form" enctype="multipart/form-data">
|
|
12465
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
12466
|
+
<div class="tempest-admin-form__field">
|
|
12467
|
+
<label>
|
|
12468
|
+
<span>CSV file *</span>
|
|
12469
|
+
<input type="file" name="file" accept=".csv,text/csv" required>
|
|
12470
|
+
</label>
|
|
12471
|
+
<small class="tempest-admin-form__hint">
|
|
12472
|
+
UTF-8, comma-separated, with a header row. Recognised columns:
|
|
12473
|
+
<code>${escapeHtml(view.columns.join(", "))}</code>. Unknown columns are ignored.
|
|
12474
|
+
</small>
|
|
12475
|
+
</div>
|
|
12476
|
+
<div class="tempest-admin-form__actions">
|
|
12477
|
+
<button type="submit">Import</button>
|
|
12478
|
+
<a href="${escapeHtml(view.backUrl)}" class="tempest-admin-form__cancel">Cancel</a>
|
|
12479
|
+
</div>
|
|
12480
|
+
</form>
|
|
12481
|
+
</section>`;
|
|
12482
|
+
return renderLayout(context, `Import ${view.title} \xB7 ${context.site.title}`, body);
|
|
12483
|
+
}
|
|
12484
|
+
var AUTOCOMPLETE_SCRIPT = `<script>
|
|
12485
|
+
(function () {
|
|
12486
|
+
document.querySelectorAll("[data-ac]").forEach(function (box) {
|
|
12487
|
+
var search = box.querySelector("[data-ac-search]");
|
|
12488
|
+
var value = box.querySelector("[data-ac-value]");
|
|
12489
|
+
var results = box.querySelector("[data-ac-results]");
|
|
12490
|
+
var url = box.getAttribute("data-ac-url");
|
|
12491
|
+
var timer = null;
|
|
12492
|
+
|
|
12493
|
+
function close() {
|
|
12494
|
+
results.hidden = true;
|
|
12495
|
+
results.innerHTML = "";
|
|
12496
|
+
}
|
|
12497
|
+
|
|
12498
|
+
function pick(option) {
|
|
12499
|
+
value.value = option.value;
|
|
12500
|
+
search.value = option.label;
|
|
12501
|
+
close();
|
|
12502
|
+
}
|
|
12503
|
+
|
|
12504
|
+
function run() {
|
|
12505
|
+
var term = search.value.trim();
|
|
12506
|
+
fetch(url + "?q=" + encodeURIComponent(term), { credentials: "same-origin" })
|
|
12507
|
+
.then(function (response) { return response.ok ? response.json() : { options: [] }; })
|
|
12508
|
+
.then(function (payload) {
|
|
12509
|
+
results.innerHTML = "";
|
|
12510
|
+
(payload.options || []).forEach(function (option) {
|
|
12511
|
+
var item = document.createElement("li");
|
|
12512
|
+
item.textContent = option.label;
|
|
12513
|
+
item.setAttribute("role", "option");
|
|
12514
|
+
item.addEventListener("mousedown", function (event) {
|
|
12515
|
+
event.preventDefault();
|
|
12516
|
+
pick(option);
|
|
12517
|
+
});
|
|
12518
|
+
results.appendChild(item);
|
|
12519
|
+
});
|
|
12520
|
+
results.hidden = results.children.length === 0;
|
|
12521
|
+
})
|
|
12522
|
+
.catch(close);
|
|
12523
|
+
}
|
|
12524
|
+
|
|
12525
|
+
search.addEventListener("input", function () {
|
|
12526
|
+
value.value = "";
|
|
12527
|
+
window.clearTimeout(timer);
|
|
12528
|
+
timer = window.setTimeout(run, 250);
|
|
12529
|
+
});
|
|
12530
|
+
search.addEventListener("focus", run);
|
|
12531
|
+
search.addEventListener("blur", function () { window.setTimeout(close, 150); });
|
|
12532
|
+
});
|
|
12533
|
+
})();
|
|
12534
|
+
</script>`;
|
|
12219
12535
|
var logger2 = new JSONLogger("tempest_express_sdk.admin.router");
|
|
12536
|
+
var INLINE_ROW_LIMIT = 50;
|
|
12537
|
+
var AUTOCOMPLETE_LIMIT = 20;
|
|
12220
12538
|
var AUDIT_HISTORY_LIMIT = 50;
|
|
12221
12539
|
var FK_OPTION_CAP = 1e3;
|
|
12222
12540
|
var FLASH_MAX_LENGTH = 300;
|
|
@@ -12246,6 +12564,7 @@ function makeAdminRouter(site, options) {
|
|
|
12246
12564
|
const theme = resolveAdminTheme(site.theme);
|
|
12247
12565
|
const showMetrics = options.showMetrics ?? true;
|
|
12248
12566
|
const exportMaxRows = options.exportMaxRows ?? 5e3;
|
|
12567
|
+
const maxUploadBytes = options.maxUploadBytes ?? 10 * 1024 * 1024;
|
|
12249
12568
|
const sessions = new AdminSessionStore({
|
|
12250
12569
|
secret: options.secretKey,
|
|
12251
12570
|
...options.cookieName === void 0 ? {} : { cookieName: options.cookieName },
|
|
@@ -12503,7 +12822,8 @@ function makeAdminRouter(site, options) {
|
|
|
12503
12822
|
mode: "create",
|
|
12504
12823
|
title: admin.verboseName(),
|
|
12505
12824
|
fields: buildFormFields(admin, {
|
|
12506
|
-
foreignKeyOptions: await foreignKeyOptionsFor(admin, state.dbSession)
|
|
12825
|
+
foreignKeyOptions: await foreignKeyOptionsFor(admin, state.dbSession),
|
|
12826
|
+
autocompleteUrls: autocompleteUrlsFor(admin)
|
|
12507
12827
|
}),
|
|
12508
12828
|
actionUrl: `${prefix}/m/${admin.slug()}/new`,
|
|
12509
12829
|
backUrl: `${prefix}/m/${admin.slug()}`,
|
|
@@ -12523,10 +12843,28 @@ function makeAdminRouter(site, options) {
|
|
|
12523
12843
|
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
12524
12844
|
return;
|
|
12525
12845
|
}
|
|
12526
|
-
|
|
12527
|
-
|
|
12846
|
+
let uploadError = null;
|
|
12847
|
+
let submission;
|
|
12848
|
+
try {
|
|
12849
|
+
submission = await readSubmission(req);
|
|
12850
|
+
} catch (error) {
|
|
12851
|
+
if (!(error instanceof MultipartLimitError)) throw error;
|
|
12852
|
+
submission = { fields: {}, files: [] };
|
|
12853
|
+
uploadError = error.message;
|
|
12854
|
+
}
|
|
12855
|
+
const body = submission.fields;
|
|
12856
|
+
if (uploadError === null && !csrfTokenMatches(state.session, body.csrf_token)) {
|
|
12857
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 403);
|
|
12858
|
+
return;
|
|
12859
|
+
}
|
|
12528
12860
|
const parsed = parseFormBody(admin, body);
|
|
12861
|
+
await applyUploads(admin, parsed.data, parsed.errors, submission.files, true);
|
|
12529
12862
|
const foreignKeyOptions = await foreignKeyOptionsFor(admin, state.dbSession);
|
|
12863
|
+
const autocompleteLabels = await autocompleteLabelsFor(
|
|
12864
|
+
admin,
|
|
12865
|
+
body,
|
|
12866
|
+
state.dbSession
|
|
12867
|
+
);
|
|
12530
12868
|
const rerender = (error, status) => {
|
|
12531
12869
|
html(
|
|
12532
12870
|
res,
|
|
@@ -12536,7 +12874,9 @@ function makeAdminRouter(site, options) {
|
|
|
12536
12874
|
fields: buildFormFields(admin, {
|
|
12537
12875
|
values: body,
|
|
12538
12876
|
errors: parsed.errors,
|
|
12539
|
-
foreignKeyOptions
|
|
12877
|
+
foreignKeyOptions,
|
|
12878
|
+
autocompleteUrls: autocompleteUrlsFor(admin),
|
|
12879
|
+
autocompleteLabels
|
|
12540
12880
|
}),
|
|
12541
12881
|
actionUrl: `${prefix}/m/${admin.slug()}/new`,
|
|
12542
12882
|
backUrl: `${prefix}/m/${admin.slug()}`,
|
|
@@ -12545,6 +12885,10 @@ function makeAdminRouter(site, options) {
|
|
|
12545
12885
|
status
|
|
12546
12886
|
);
|
|
12547
12887
|
};
|
|
12888
|
+
if (uploadError !== null) {
|
|
12889
|
+
rerender(uploadError, 400);
|
|
12890
|
+
return;
|
|
12891
|
+
}
|
|
12548
12892
|
if (Object.keys(parsed.errors).length > 0) {
|
|
12549
12893
|
rerender("Please fix the highlighted fields.", 400);
|
|
12550
12894
|
return;
|
|
@@ -12664,6 +13008,129 @@ function makeAdminRouter(site, options) {
|
|
|
12664
13008
|
);
|
|
12665
13009
|
})
|
|
12666
13010
|
);
|
|
13011
|
+
router.get(
|
|
13012
|
+
`${prefix}/m/:slug/import`,
|
|
13013
|
+
guarded(async (req, res) => {
|
|
13014
|
+
const state = await authenticate(req, res);
|
|
13015
|
+
if (state === null) return;
|
|
13016
|
+
const admin = await resolveAdmin(req, res, state, AdminPermission.CREATE);
|
|
13017
|
+
if (admin === null) return;
|
|
13018
|
+
if (!admin.canImport) {
|
|
13019
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13020
|
+
return;
|
|
13021
|
+
}
|
|
13022
|
+
html(res, renderImport(req, state, admin, null, null, []));
|
|
13023
|
+
})
|
|
13024
|
+
);
|
|
13025
|
+
router.post(
|
|
13026
|
+
`${prefix}/m/:slug/import`,
|
|
13027
|
+
guarded(async (req, res) => {
|
|
13028
|
+
const state = await authenticate(req, res);
|
|
13029
|
+
if (state === null) return;
|
|
13030
|
+
const admin = await resolveAdmin(req, res, state, AdminPermission.CREATE);
|
|
13031
|
+
if (admin === null) return;
|
|
13032
|
+
if (!admin.canImport) {
|
|
13033
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13034
|
+
return;
|
|
13035
|
+
}
|
|
13036
|
+
let submission;
|
|
13037
|
+
try {
|
|
13038
|
+
submission = await readSubmission(req);
|
|
13039
|
+
} catch (error) {
|
|
13040
|
+
if (!(error instanceof MultipartLimitError)) throw error;
|
|
13041
|
+
html(res, renderImport(req, state, admin, error.message, null, []), 400);
|
|
13042
|
+
return;
|
|
13043
|
+
}
|
|
13044
|
+
if (!csrfTokenMatches(state.session, submission.fields.csrf_token)) {
|
|
13045
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 403);
|
|
13046
|
+
return;
|
|
13047
|
+
}
|
|
13048
|
+
const file = submission.files[0];
|
|
13049
|
+
if (file === void 0) {
|
|
13050
|
+
html(
|
|
13051
|
+
res,
|
|
13052
|
+
renderImport(req, state, admin, "Choose a CSV file to import.", null, []),
|
|
13053
|
+
400
|
|
13054
|
+
);
|
|
13055
|
+
return;
|
|
13056
|
+
}
|
|
13057
|
+
let rows;
|
|
13058
|
+
try {
|
|
13059
|
+
rows = parseCsv(file.data.toString("utf8"));
|
|
13060
|
+
} catch (error) {
|
|
13061
|
+
const message = error instanceof Error ? error.message : "Could not read the file as UTF-8 CSV.";
|
|
13062
|
+
html(res, renderImport(req, state, admin, message, null, []), 400);
|
|
13063
|
+
return;
|
|
13064
|
+
}
|
|
13065
|
+
const repository = admin.repository(state.dbSession);
|
|
13066
|
+
const actorId = backend.principalId(state.principal);
|
|
13067
|
+
const rowErrors = [];
|
|
13068
|
+
let created = 0;
|
|
13069
|
+
for (const [index, row] of rows.entries()) {
|
|
13070
|
+
const parsed = parseFormBody(admin, row, { uploadsAsText: true });
|
|
13071
|
+
const failures = Object.entries(parsed.errors);
|
|
13072
|
+
if (failures.length > 0) {
|
|
13073
|
+
rowErrors.push({
|
|
13074
|
+
row: index + 2,
|
|
13075
|
+
message: failures.map(([field, error]) => `${field}: ${error}`).join("; ")
|
|
13076
|
+
});
|
|
13077
|
+
continue;
|
|
13078
|
+
}
|
|
13079
|
+
stampActor(admin, parsed.data, actorId, true);
|
|
13080
|
+
try {
|
|
13081
|
+
await repository.create(parsed.data);
|
|
13082
|
+
created += 1;
|
|
13083
|
+
} catch (error) {
|
|
13084
|
+
rowErrors.push({
|
|
13085
|
+
row: index + 2,
|
|
13086
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13087
|
+
});
|
|
13088
|
+
}
|
|
13089
|
+
}
|
|
13090
|
+
html(res, renderImport(req, state, admin, null, created, rowErrors));
|
|
13091
|
+
})
|
|
13092
|
+
);
|
|
13093
|
+
router.get(
|
|
13094
|
+
`${prefix}/m/:slug/autocomplete/:field`,
|
|
13095
|
+
guarded(async (req, res) => {
|
|
13096
|
+
if (sessions.load(req) === null) {
|
|
13097
|
+
res.status(401).json({ options: [] });
|
|
13098
|
+
return;
|
|
13099
|
+
}
|
|
13100
|
+
const state = await authenticate(req, res);
|
|
13101
|
+
if (state === null) return;
|
|
13102
|
+
const admin = site.get(String(req.params.slug));
|
|
13103
|
+
const field = String(req.params.field);
|
|
13104
|
+
if (admin === null || !admin.autocompleteFields.includes(field) || !await allows(state.principal, admin, AdminPermission.VIEW)) {
|
|
13105
|
+
res.status(404).json({ options: [] });
|
|
13106
|
+
return;
|
|
13107
|
+
}
|
|
13108
|
+
const column6 = adminColumns(admin.model)[field];
|
|
13109
|
+
const table = column6 === void 0 ? null : foreignKeyTable(column6);
|
|
13110
|
+
const referenced = table === null ? null : site.get(table);
|
|
13111
|
+
if (referenced === null) {
|
|
13112
|
+
res.json({ options: [] });
|
|
13113
|
+
return;
|
|
13114
|
+
}
|
|
13115
|
+
const term = queryString(req.query.q);
|
|
13116
|
+
const searchable = referenced.searchFields.filter((name) => {
|
|
13117
|
+
const target = adminColumns(referenced.model)[name];
|
|
13118
|
+
return target !== void 0 && isSearchableColumn(target);
|
|
13119
|
+
});
|
|
13120
|
+
const filters = term === "" || searchable.length === 0 ? void 0 : or(...searchable.map((name) => ({ [name]: { ilike: `%${term}%` } })));
|
|
13121
|
+
const page2 = await referenced.repository(state.dbSession).paginate({
|
|
13122
|
+
page: 1,
|
|
13123
|
+
pageSize: AUTOCOMPLETE_LIMIT,
|
|
13124
|
+
...filters === void 0 ? {} : { filters }
|
|
13125
|
+
});
|
|
13126
|
+
res.json({
|
|
13127
|
+
options: page2.items.map((row) => ({
|
|
13128
|
+
value: String(row[referenced.identityField]),
|
|
13129
|
+
label: foreignKeyLabel(referenced, row)
|
|
13130
|
+
}))
|
|
13131
|
+
});
|
|
13132
|
+
})
|
|
13133
|
+
);
|
|
12667
13134
|
router.get(
|
|
12668
13135
|
`${prefix}/m/:slug/:identity`,
|
|
12669
13136
|
guarded(async (req, res) => {
|
|
@@ -12683,6 +13150,8 @@ function makeAdminRouter(site, options) {
|
|
|
12683
13150
|
title: admin.verboseName(),
|
|
12684
13151
|
identity,
|
|
12685
13152
|
audit: await buildAuditView(admin, row, state.dbSession),
|
|
13153
|
+
inlines: await buildInlines(admin, row, state, identity),
|
|
13154
|
+
inlineError: null,
|
|
12686
13155
|
fields: admin.detailFieldNames().map((name) => ({ label: name, value: formatCellValue(row[name]) })),
|
|
12687
13156
|
backUrl: `${prefix}/m/${admin.slug()}`,
|
|
12688
13157
|
editUrl: await allows(state.principal, admin, AdminPermission.EDIT) ? `${prefix}/m/${admin.slug()}/${identity}/edit` : null,
|
|
@@ -12711,7 +13180,9 @@ function makeAdminRouter(site, options) {
|
|
|
12711
13180
|
title: admin.verboseName(),
|
|
12712
13181
|
fields: buildFormFields(admin, {
|
|
12713
13182
|
values: row,
|
|
12714
|
-
foreignKeyOptions: await foreignKeyOptionsFor(admin, state.dbSession)
|
|
13183
|
+
foreignKeyOptions: await foreignKeyOptionsFor(admin, state.dbSession),
|
|
13184
|
+
autocompleteUrls: autocompleteUrlsFor(admin),
|
|
13185
|
+
autocompleteLabels: await autocompleteLabelsFor(admin, row, state.dbSession)
|
|
12715
13186
|
}),
|
|
12716
13187
|
actionUrl: `${prefix}/m/${admin.slug()}/${identity}/edit`,
|
|
12717
13188
|
backUrl: `${prefix}/m/${admin.slug()}/${identity}`,
|
|
@@ -12732,10 +13203,28 @@ function makeAdminRouter(site, options) {
|
|
|
12732
13203
|
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
12733
13204
|
return;
|
|
12734
13205
|
}
|
|
12735
|
-
|
|
12736
|
-
|
|
13206
|
+
let uploadError = null;
|
|
13207
|
+
let submission;
|
|
13208
|
+
try {
|
|
13209
|
+
submission = await readSubmission(req);
|
|
13210
|
+
} catch (error) {
|
|
13211
|
+
if (!(error instanceof MultipartLimitError)) throw error;
|
|
13212
|
+
submission = { fields: {}, files: [] };
|
|
13213
|
+
uploadError = error.message;
|
|
13214
|
+
}
|
|
13215
|
+
const body = submission.fields;
|
|
13216
|
+
if (uploadError === null && !csrfTokenMatches(state.session, body.csrf_token)) {
|
|
13217
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 403);
|
|
13218
|
+
return;
|
|
13219
|
+
}
|
|
12737
13220
|
const parsed = parseFormBody(admin, body);
|
|
13221
|
+
await applyUploads(admin, parsed.data, parsed.errors, submission.files, false);
|
|
12738
13222
|
const foreignKeyOptions = await foreignKeyOptionsFor(admin, state.dbSession);
|
|
13223
|
+
const autocompleteLabels = await autocompleteLabelsFor(
|
|
13224
|
+
admin,
|
|
13225
|
+
body,
|
|
13226
|
+
state.dbSession
|
|
13227
|
+
);
|
|
12739
13228
|
const rerender = (error, status) => {
|
|
12740
13229
|
html(
|
|
12741
13230
|
res,
|
|
@@ -12745,7 +13234,9 @@ function makeAdminRouter(site, options) {
|
|
|
12745
13234
|
fields: buildFormFields(admin, {
|
|
12746
13235
|
values: body,
|
|
12747
13236
|
errors: parsed.errors,
|
|
12748
|
-
foreignKeyOptions
|
|
13237
|
+
foreignKeyOptions,
|
|
13238
|
+
autocompleteUrls: autocompleteUrlsFor(admin),
|
|
13239
|
+
autocompleteLabels
|
|
12749
13240
|
}),
|
|
12750
13241
|
actionUrl: `${prefix}/m/${admin.slug()}/${identity}/edit`,
|
|
12751
13242
|
backUrl: `${prefix}/m/${admin.slug()}/${identity}`,
|
|
@@ -12754,6 +13245,10 @@ function makeAdminRouter(site, options) {
|
|
|
12754
13245
|
status
|
|
12755
13246
|
);
|
|
12756
13247
|
};
|
|
13248
|
+
if (uploadError !== null) {
|
|
13249
|
+
rerender(uploadError, 400);
|
|
13250
|
+
return;
|
|
13251
|
+
}
|
|
12757
13252
|
if (Object.keys(parsed.errors).length > 0) {
|
|
12758
13253
|
rerender("Please fix the highlighted fields.", 400);
|
|
12759
13254
|
return;
|
|
@@ -12777,6 +13272,103 @@ function makeAdminRouter(site, options) {
|
|
|
12777
13272
|
res.redirect(`${prefix}/m/${admin.slug()}/${identity}?ok=updated`);
|
|
12778
13273
|
})
|
|
12779
13274
|
);
|
|
13275
|
+
router.post(
|
|
13276
|
+
`${prefix}/m/:slug/:identity/inlines/:child`,
|
|
13277
|
+
guarded(async (req, res) => {
|
|
13278
|
+
const state = await authenticate(req, res);
|
|
13279
|
+
if (state === null) return;
|
|
13280
|
+
const admin = await resolveAdmin(req, res, state);
|
|
13281
|
+
if (admin === null) return;
|
|
13282
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13283
|
+
const childSlug = String(req.params.child);
|
|
13284
|
+
const inline = admin.inlines.find(
|
|
13285
|
+
(entry) => entry.slug === childSlug && entry.editable
|
|
13286
|
+
);
|
|
13287
|
+
const childAdmin = inline === void 0 ? null : site.get(childSlug);
|
|
13288
|
+
if (inline === void 0 || childAdmin === null || !await allows(state.principal, childAdmin, AdminPermission.EDIT)) {
|
|
13289
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13290
|
+
return;
|
|
13291
|
+
}
|
|
13292
|
+
const identity = String(req.params.identity);
|
|
13293
|
+
const parent = await findRow(admin, state.dbSession, identity);
|
|
13294
|
+
if (parent === null) {
|
|
13295
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13296
|
+
return;
|
|
13297
|
+
}
|
|
13298
|
+
const parentId = parent[admin.identityField];
|
|
13299
|
+
const { rows: grouped, deletions } = groupInlineSubmission(
|
|
13300
|
+
req.body
|
|
13301
|
+
);
|
|
13302
|
+
const names = inlineFieldNames(childAdmin, inline);
|
|
13303
|
+
const childRepo = childAdmin.repository(state.dbSession);
|
|
13304
|
+
const actorId = backend.principalId(state.principal);
|
|
13305
|
+
const canDelete = inline.canDelete && await allows(state.principal, childAdmin, AdminPermission.DELETE);
|
|
13306
|
+
const failed = [];
|
|
13307
|
+
let formError = null;
|
|
13308
|
+
const owned = async (key) => await childRepo.first({
|
|
13309
|
+
[childAdmin.identityField]: key,
|
|
13310
|
+
[inline.fkField]: parentId
|
|
13311
|
+
});
|
|
13312
|
+
for (const [key, values] of Object.entries(grouped)) {
|
|
13313
|
+
const isNew = key.startsWith("new");
|
|
13314
|
+
if (!isNew && canDelete && deletions.has(key)) {
|
|
13315
|
+
if (await owned(key) !== null) {
|
|
13316
|
+
await childRepo.delete({ [childAdmin.identityField]: key });
|
|
13317
|
+
}
|
|
13318
|
+
continue;
|
|
13319
|
+
}
|
|
13320
|
+
if (isNew && Object.values(values).every((value) => value.trim() === "")) {
|
|
13321
|
+
continue;
|
|
13322
|
+
}
|
|
13323
|
+
const parsed = parseFormBody(childAdmin, values, { only: names });
|
|
13324
|
+
if (Object.keys(parsed.errors).length > 0) {
|
|
13325
|
+
failed.push({ key, values, errors: parsed.errors });
|
|
13326
|
+
continue;
|
|
13327
|
+
}
|
|
13328
|
+
try {
|
|
13329
|
+
if (isNew) {
|
|
13330
|
+
stampActor(childAdmin, parsed.data, actorId, true);
|
|
13331
|
+
await childRepo.create({
|
|
13332
|
+
...parsed.data,
|
|
13333
|
+
[inline.fkField]: parentId
|
|
13334
|
+
});
|
|
13335
|
+
} else {
|
|
13336
|
+
if (await owned(key) === null) continue;
|
|
13337
|
+
stampActor(childAdmin, parsed.data, actorId, false);
|
|
13338
|
+
await childRepo.update(
|
|
13339
|
+
{ [childAdmin.identityField]: key },
|
|
13340
|
+
parsed.data
|
|
13341
|
+
);
|
|
13342
|
+
}
|
|
13343
|
+
} catch (error) {
|
|
13344
|
+
formError = describeWriteFailure(childAdmin, error);
|
|
13345
|
+
failed.push({ key, values, errors: {} });
|
|
13346
|
+
}
|
|
13347
|
+
}
|
|
13348
|
+
if (failed.length > 0) {
|
|
13349
|
+
const fresh = await findRow(admin, state.dbSession, identity) ?? parent;
|
|
13350
|
+
html(
|
|
13351
|
+
res,
|
|
13352
|
+
renderDetailPage(context(req, state.session, state.visible), {
|
|
13353
|
+
title: admin.verboseName(),
|
|
13354
|
+
identity,
|
|
13355
|
+
audit: await buildAuditView(admin, fresh, state.dbSession),
|
|
13356
|
+
inlines: await buildInlines(admin, fresh, state, identity, {
|
|
13357
|
+
[childSlug]: failed
|
|
13358
|
+
}),
|
|
13359
|
+
inlineError: formError ?? "Some inline rows could not be saved.",
|
|
13360
|
+
fields: admin.detailFieldNames().map((name) => ({ label: name, value: formatCellValue(fresh[name]) })),
|
|
13361
|
+
backUrl: `${prefix}/m/${admin.slug()}`,
|
|
13362
|
+
editUrl: await allows(state.principal, admin, AdminPermission.EDIT) ? `${prefix}/m/${admin.slug()}/${identity}/edit` : null,
|
|
13363
|
+
deleteUrl: await allows(state.principal, admin, AdminPermission.DELETE) ? `${prefix}/m/${admin.slug()}/${identity}/delete` : null
|
|
13364
|
+
}),
|
|
13365
|
+
400
|
|
13366
|
+
);
|
|
13367
|
+
return;
|
|
13368
|
+
}
|
|
13369
|
+
res.redirect(`${prefix}/m/${admin.slug()}/${identity}?ok=updated`);
|
|
13370
|
+
})
|
|
13371
|
+
);
|
|
12780
13372
|
router.post(
|
|
12781
13373
|
`${prefix}/m/:slug/:identity/delete`,
|
|
12782
13374
|
guarded(async (req, res) => {
|
|
@@ -12798,6 +13390,89 @@ function makeAdminRouter(site, options) {
|
|
|
12798
13390
|
const principal = await backend.loadPrincipal(dbSession, String(actor));
|
|
12799
13391
|
return principal === null ? String(actor) : backend.displayName(principal);
|
|
12800
13392
|
}
|
|
13393
|
+
async function buildInlines(admin, parent, state, identity, overrides = {}) {
|
|
13394
|
+
const parentId = parent[admin.identityField];
|
|
13395
|
+
const blocks = [];
|
|
13396
|
+
for (const inline of admin.inlines) {
|
|
13397
|
+
const childAdmin = site.get(inline.slug);
|
|
13398
|
+
const repository = childAdmin === null ? new BaseRepository(inline.model, state.dbSession) : childAdmin.repository(state.dbSession);
|
|
13399
|
+
const children = await repository.list({
|
|
13400
|
+
[inline.fkField]: parentId
|
|
13401
|
+
});
|
|
13402
|
+
const columns = inline.listDisplay ?? childAdmin?.listDisplayNames() ?? Object.keys(adminColumns(inline.model));
|
|
13403
|
+
const label = inline.label ?? childAdmin?.verboseNamePlural() ?? humanizeField(inline.slug);
|
|
13404
|
+
const visible = children.slice(0, INLINE_ROW_LIMIT);
|
|
13405
|
+
const editable = inline.editable && childAdmin !== null && await allows(state.principal, childAdmin, AdminPermission.EDIT);
|
|
13406
|
+
const addUrl = childAdmin !== null && await allows(state.principal, childAdmin, AdminPermission.CREATE) ? `${prefix}/m/${inline.slug}/new` : null;
|
|
13407
|
+
if (!editable || childAdmin === null) {
|
|
13408
|
+
blocks.push({
|
|
13409
|
+
label,
|
|
13410
|
+
total: children.length,
|
|
13411
|
+
columns,
|
|
13412
|
+
editable: false,
|
|
13413
|
+
canDelete: false,
|
|
13414
|
+
addUrl,
|
|
13415
|
+
formAction: "",
|
|
13416
|
+
rows: visible.map((child) => ({
|
|
13417
|
+
key: String(child[childAdmin?.identityField ?? "id"]),
|
|
13418
|
+
cells: columns.map((column6) => formatCellValue(child[column6])),
|
|
13419
|
+
fields: [],
|
|
13420
|
+
url: childAdmin === null ? null : `${prefix}/m/${inline.slug}/${String(child[childAdmin.identityField])}`
|
|
13421
|
+
})),
|
|
13422
|
+
newRow: null,
|
|
13423
|
+
truncated: children.length > visible.length
|
|
13424
|
+
});
|
|
13425
|
+
continue;
|
|
13426
|
+
}
|
|
13427
|
+
const names = inlineFieldNames(childAdmin, inline);
|
|
13428
|
+
const submitted = overrides[inline.slug];
|
|
13429
|
+
const rows = [];
|
|
13430
|
+
if (submitted !== void 0) {
|
|
13431
|
+
for (const entry of submitted) {
|
|
13432
|
+
rows.push({
|
|
13433
|
+
key: entry.key,
|
|
13434
|
+
cells: [],
|
|
13435
|
+
fields: inlineFields(
|
|
13436
|
+
childAdmin,
|
|
13437
|
+
names,
|
|
13438
|
+
entry.key,
|
|
13439
|
+
entry.values,
|
|
13440
|
+
entry.errors
|
|
13441
|
+
),
|
|
13442
|
+
url: null
|
|
13443
|
+
});
|
|
13444
|
+
}
|
|
13445
|
+
} else {
|
|
13446
|
+
for (const child of visible) {
|
|
13447
|
+
const key = String(child[childAdmin.identityField]);
|
|
13448
|
+
rows.push({
|
|
13449
|
+
key,
|
|
13450
|
+
cells: [],
|
|
13451
|
+
fields: inlineFields(childAdmin, names, key, child, {}),
|
|
13452
|
+
url: `${prefix}/m/${inline.slug}/${key}`
|
|
13453
|
+
});
|
|
13454
|
+
}
|
|
13455
|
+
}
|
|
13456
|
+
blocks.push({
|
|
13457
|
+
label,
|
|
13458
|
+
total: children.length,
|
|
13459
|
+
columns: names.map(humanizeField),
|
|
13460
|
+
editable: true,
|
|
13461
|
+
canDelete: inline.canDelete && childAdmin.canDelete,
|
|
13462
|
+
addUrl,
|
|
13463
|
+
formAction: `${prefix}/m/${admin.slug()}/${identity}/inlines/${inline.slug}`,
|
|
13464
|
+
rows,
|
|
13465
|
+
newRow: {
|
|
13466
|
+
key: "new1",
|
|
13467
|
+
cells: [],
|
|
13468
|
+
fields: inlineFields(childAdmin, names, "new1", {}, {}),
|
|
13469
|
+
url: null
|
|
13470
|
+
},
|
|
13471
|
+
truncated: children.length > visible.length
|
|
13472
|
+
});
|
|
13473
|
+
}
|
|
13474
|
+
return blocks;
|
|
13475
|
+
}
|
|
12801
13476
|
async function buildAuditView(admin, row, dbSession) {
|
|
12802
13477
|
const fields = [];
|
|
12803
13478
|
for (const name of admin.auditFieldNames()) {
|
|
@@ -12837,6 +13512,44 @@ function makeAdminRouter(site, options) {
|
|
|
12837
13512
|
const row = await admin.repository(dbSession).first({ [admin.identityField]: identity });
|
|
12838
13513
|
return row ?? null;
|
|
12839
13514
|
}
|
|
13515
|
+
function renderImport(req, state, admin, error, created, rowErrors) {
|
|
13516
|
+
return renderImportPage(context(req, state.session, state.visible), {
|
|
13517
|
+
title: admin.verboseNamePlural(),
|
|
13518
|
+
actionUrl: `${prefix}/m/${admin.slug()}/import`,
|
|
13519
|
+
backUrl: `${prefix}/m/${admin.slug()}`,
|
|
13520
|
+
columns: admin.editableFieldNames(),
|
|
13521
|
+
error,
|
|
13522
|
+
created,
|
|
13523
|
+
rowErrors
|
|
13524
|
+
});
|
|
13525
|
+
}
|
|
13526
|
+
async function readSubmission(req) {
|
|
13527
|
+
if (!isMultipart(req)) {
|
|
13528
|
+
return { fields: req.body ?? {}, files: [] };
|
|
13529
|
+
}
|
|
13530
|
+
const parsed = await parseMultipart(req, { maxFileBytes: maxUploadBytes });
|
|
13531
|
+
return { fields: parsed.fields, files: parsed.files };
|
|
13532
|
+
}
|
|
13533
|
+
async function applyUploads(admin, data, errors, files, creating) {
|
|
13534
|
+
if (admin.uploadFields.length === 0) return;
|
|
13535
|
+
const storage2 = admin.uploadStorage;
|
|
13536
|
+
if (storage2 === null) return;
|
|
13537
|
+
const columns = adminColumns(admin.model);
|
|
13538
|
+
for (const field of admin.uploadFields) {
|
|
13539
|
+
const file = files.find((candidate) => candidate.field === field);
|
|
13540
|
+
if (file === void 0) {
|
|
13541
|
+
const column6 = columns[field];
|
|
13542
|
+
if (creating && column6 !== void 0 && !isColumnOptional(column6)) {
|
|
13543
|
+
errors[field] = "This field is required.";
|
|
13544
|
+
}
|
|
13545
|
+
continue;
|
|
13546
|
+
}
|
|
13547
|
+
const extension = file.filename.includes(".") ? `.${file.filename.split(".").pop()}` : "";
|
|
13548
|
+
const key = `${admin.slug()}/${field}/${randomUUID()}${extension}`;
|
|
13549
|
+
const saved = await storage2.save(key, file.data, { contentType: file.contentType });
|
|
13550
|
+
data[field] = saved.key;
|
|
13551
|
+
}
|
|
13552
|
+
}
|
|
12840
13553
|
async function permittedBulkActions(admin, principal) {
|
|
12841
13554
|
const permitted = [];
|
|
12842
13555
|
for (const option of bulkActionsFor(admin)) {
|
|
@@ -12900,6 +13613,7 @@ function makeAdminRouter(site, options) {
|
|
|
12900
13613
|
filters: query.filterViews,
|
|
12901
13614
|
sort,
|
|
12902
13615
|
newUrl: await allows(state.principal, admin, AdminPermission.CREATE) ? `${prefix}/m/${admin.slug()}/new` : null,
|
|
13616
|
+
importUrl: admin.canImport && await allows(state.principal, admin, AdminPermission.CREATE) ? `${prefix}/m/${admin.slug()}/import` : null,
|
|
12903
13617
|
bulkActions: await permittedBulkActions(admin, state.principal),
|
|
12904
13618
|
bulkUrl: `${prefix}/m/${admin.slug()}/bulk`,
|
|
12905
13619
|
exportCsvUrl: exportUrl("csv"),
|
|
@@ -13024,6 +13738,7 @@ function makeAdminRouter(site, options) {
|
|
|
13024
13738
|
const columns = adminColumns(admin.model);
|
|
13025
13739
|
const options2 = {};
|
|
13026
13740
|
for (const field of Object.keys(foreignKeyFields(admin))) {
|
|
13741
|
+
if (admin.autocompleteFields.includes(field)) continue;
|
|
13027
13742
|
const column6 = columns[field];
|
|
13028
13743
|
if (column6 === void 0) continue;
|
|
13029
13744
|
const related = await relatedOptions(column6, dbSession);
|
|
@@ -13031,8 +13746,60 @@ function makeAdminRouter(site, options) {
|
|
|
13031
13746
|
}
|
|
13032
13747
|
return options2;
|
|
13033
13748
|
}
|
|
13749
|
+
function autocompleteUrlsFor(admin) {
|
|
13750
|
+
const urls = {};
|
|
13751
|
+
for (const field of admin.autocompleteFields) {
|
|
13752
|
+
urls[field] = `${prefix}/m/${admin.slug()}/autocomplete/${field}`;
|
|
13753
|
+
}
|
|
13754
|
+
return urls;
|
|
13755
|
+
}
|
|
13756
|
+
async function autocompleteLabelsFor(admin, row, dbSession) {
|
|
13757
|
+
const labels = {};
|
|
13758
|
+
if (row === null) return labels;
|
|
13759
|
+
const columns = adminColumns(admin.model);
|
|
13760
|
+
for (const field of admin.autocompleteFields) {
|
|
13761
|
+
const value = row[field];
|
|
13762
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
13763
|
+
const column6 = columns[field];
|
|
13764
|
+
const table = column6 === void 0 ? null : foreignKeyTable(column6);
|
|
13765
|
+
const referenced = table === null ? null : site.get(table);
|
|
13766
|
+
if (referenced === null) continue;
|
|
13767
|
+
const related = await referenced.repository(dbSession).first({ [referenced.identityField]: value });
|
|
13768
|
+
if (related !== null) labels[field] = foreignKeyLabel(referenced, related);
|
|
13769
|
+
}
|
|
13770
|
+
return labels;
|
|
13771
|
+
}
|
|
13034
13772
|
return router;
|
|
13035
13773
|
}
|
|
13774
|
+
function groupInlineSubmission(body) {
|
|
13775
|
+
const rows = {};
|
|
13776
|
+
const deletions = /* @__PURE__ */ new Set();
|
|
13777
|
+
for (const [key, raw] of Object.entries(body)) {
|
|
13778
|
+
if (!key.startsWith("row.")) continue;
|
|
13779
|
+
const parts = key.split(".");
|
|
13780
|
+
if (parts.length !== 3) continue;
|
|
13781
|
+
const [, rowKey, field] = parts;
|
|
13782
|
+
const value = typeof raw === "string" ? raw : "";
|
|
13783
|
+
if (field === "__delete") {
|
|
13784
|
+
if (!["", "false", "off", "0", "no"].includes(value.trim().toLowerCase())) {
|
|
13785
|
+
deletions.add(rowKey);
|
|
13786
|
+
}
|
|
13787
|
+
continue;
|
|
13788
|
+
}
|
|
13789
|
+
const row = rows[rowKey] ?? {};
|
|
13790
|
+
row[field] = value;
|
|
13791
|
+
rows[rowKey] = row;
|
|
13792
|
+
}
|
|
13793
|
+
return { rows, deletions };
|
|
13794
|
+
}
|
|
13795
|
+
function inlineFieldNames(childAdmin, inline) {
|
|
13796
|
+
return childAdmin.editableFieldNames().filter(
|
|
13797
|
+
(name) => name !== inline.fkField && !childAdmin.uploadFields.includes(name) && !childAdmin.autocompleteFields.includes(name)
|
|
13798
|
+
);
|
|
13799
|
+
}
|
|
13800
|
+
function inlineFields(childAdmin, names, key, values, errors) {
|
|
13801
|
+
return buildFormFields(childAdmin, { values, errors }).filter((field) => names.includes(field.name)).map((field) => ({ ...field, name: `row.${key}.${field.name}` }));
|
|
13802
|
+
}
|
|
13036
13803
|
function flagAllows(admin, action) {
|
|
13037
13804
|
if (action === AdminPermission.CREATE) return admin.canCreate;
|
|
13038
13805
|
if (action === AdminPermission.EDIT) return admin.canEdit;
|
|
@@ -13122,6 +13889,55 @@ function bulkActionsFor(admin) {
|
|
|
13122
13889
|
}
|
|
13123
13890
|
return actions;
|
|
13124
13891
|
}
|
|
13892
|
+
function parseCsv(text) {
|
|
13893
|
+
const source = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
13894
|
+
const rows = [];
|
|
13895
|
+
let row = [];
|
|
13896
|
+
let field = "";
|
|
13897
|
+
let quoted = false;
|
|
13898
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
13899
|
+
const char = source[index];
|
|
13900
|
+
if (quoted) {
|
|
13901
|
+
if (char === '"') {
|
|
13902
|
+
if (source[index + 1] === '"') {
|
|
13903
|
+
field += '"';
|
|
13904
|
+
index += 1;
|
|
13905
|
+
} else {
|
|
13906
|
+
quoted = false;
|
|
13907
|
+
}
|
|
13908
|
+
} else {
|
|
13909
|
+
field += char;
|
|
13910
|
+
}
|
|
13911
|
+
continue;
|
|
13912
|
+
}
|
|
13913
|
+
if (char === '"') {
|
|
13914
|
+
quoted = true;
|
|
13915
|
+
} else if (char === ",") {
|
|
13916
|
+
row.push(field);
|
|
13917
|
+
field = "";
|
|
13918
|
+
} else if (char === "\n" || char === "\r") {
|
|
13919
|
+
if (char === "\r" && source[index + 1] === "\n") index += 1;
|
|
13920
|
+
row.push(field);
|
|
13921
|
+
rows.push(row);
|
|
13922
|
+
row = [];
|
|
13923
|
+
field = "";
|
|
13924
|
+
} else {
|
|
13925
|
+
field += char;
|
|
13926
|
+
}
|
|
13927
|
+
}
|
|
13928
|
+
if (field !== "" || row.length > 0) {
|
|
13929
|
+
row.push(field);
|
|
13930
|
+
rows.push(row);
|
|
13931
|
+
}
|
|
13932
|
+
const header = rows.shift();
|
|
13933
|
+
if (header === void 0 || header.length === 0) {
|
|
13934
|
+
throw new Error("The file has no header row.");
|
|
13935
|
+
}
|
|
13936
|
+
const keys = header.map((name) => name.trim());
|
|
13937
|
+
return rows.filter((entry) => entry.some((value) => value.trim() !== "")).map(
|
|
13938
|
+
(entry) => Object.fromEntries(keys.map((key, position) => [key, entry[position] ?? ""]))
|
|
13939
|
+
);
|
|
13940
|
+
}
|
|
13125
13941
|
function exportValue(value) {
|
|
13126
13942
|
if (value instanceof Date) return value.toISOString();
|
|
13127
13943
|
if (typeof value === "bigint") return value.toString();
|
|
@@ -15154,6 +15970,6 @@ async function withTestDatabase(models, fn) {
|
|
|
15154
15970
|
}
|
|
15155
15971
|
}
|
|
15156
15972
|
|
|
15157
|
-
export { ADMIN_CSS, ActivationService, AdminJsonSite, AdminModel, AdminPermission, AdminSessionStore, AdminSite, AppException, AttemptThrottle, AuditAction, BaseAuditLogModel, BaseController, BaseModel, BaseOAuthClient, BaseOutboxModel, BaseService, BaseUserModel, BaseUserRefreshTokenModel, BaseUserTokenModel, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_DOCS_FAVICON, DEFAULT_LOCALE, EmailProvider, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, GitHubOAuthClient, GoogleOAuthClient, GracefulShutdown, HTTPClient, HTTP_500_LOG_FILE, HTTP_500_MARKER, HttpMetrics, IDEMPOTENCY_HEADER, InvalidTokenException, JSONLogger, JWTUtils, LEVEL_LOG_FILES, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemoryIdempotencyStore, MemoryRateLimitStore, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MessagingHub, MetricsUtils, MfaService, NotFoundException, OAuthError, OIDCProvider, OutboxRelay, OutboxStatus, PHONE_BR_PATTERN, PasswordResetService, REDOC_CDN_URL, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, RedisIdempotencyStore, RedisRateLimitStore, RedisSSEBroker, RedisSessionStore, Region, RetryPolicy, S3UploadStorage, SSEBroker, ServerSentEvent, SessionService, TOTPHelper, TaskManager, TelegramProvider, TenantScopedRepository, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, UserModelAuthBackend, UserTokenPurpose, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WebhookSignatureVerifier, WhatsAppProvider, activationSchema, addLogSink, adminAction, adminColumns, adminLens, adminThemeCss, attachWebSocketHub, authResponseSchema, authSettingsShape, backupDatabase, bearerToken, bodySizeLimitMiddleware, broadcastText, buildContentDisposition, buildFormFields, buildPaginationLinkHeader, cached2 as cached, cepField, citiesByUf, cnpjField, coerceFlag, configureFileLogging, configureLogging, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createTestDatabase, createdByColumn, csrfMiddleware, csrfTokenMatches, cursorPaginationFilterSchema, cursorPaginationSchema, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, diffSnapshots, emailSettingsShape, encodeCursor, envList, escapeHtml, filterForColumn, foreignKeyFields, foreignKeyLabel, foreignKeyTable, formatCellValue, formatFieldValue, generateCsrfToken, generateOAuthState, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, hashOpaqueToken, humanizeField, idempotencyMiddleware, inboundMessageSchema, isColumnOptional, isSearchableColumn, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, jwtSettingsShape, keyByHeader, keyByIp, keyByJwtClaim, keyByJwtSubject, listStates, logEntrySchema, logSettingsShape, loginSchema, makeAdminJsonRouter, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeLogsRouter, makeMetricsRouter, makeSessionMiddleware, makeToolSpecRouter, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, metricCard, mfaChallengeSchema, mfaCodeSchema, mfaEnrollResponseSchema, minioSettingsShape, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, parseFormBody, partitionTotal, passwordResetConfirmSchema, passwordResetRequestSchema, phoneBrField, prometheusMiddleware, rabbitmqSettingsShape, rateLimitMiddleware, redisSettingsShape, refreshSchema, registerExceptionHandlers, renderAuthResultPage, renderDashboardPage, renderDetailPage, renderFormPage, renderLayout, renderListPage, renderLoginPage, renderMfaPage, renderPasswordResetFormPage, requestIdMiddleware, requestTracingMiddleware, requireRoles, resolveAdminTheme, resolveDownloadPath, resolveRedocBundle, runServer, runWithRequestContext, sendBytesDownload, sendFileDownload, sessionCookie, sessionSettingsShape, setRequestId, signupSchema, snapshot, sseResponse, statesByRegion, syncFilterSchema, syncPaginationSchema, tableNameFor, toUtc, tokenFromUrl, tokenPairSchema, tokenSettingsShape, trendDirection, trendPercent, ufField, updatedByColumn, uploadSettingsShape, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSettingsShape, webPushSubscriptionSchema, webSocketSettingsShape, widgetForColumn, withTestDatabase, wrapWithSlowQueryLog, wsEnvelopeSchema };
|
|
15973
|
+
export { ADMIN_CSS, ActivationService, AdminJsonSite, AdminModel, AdminPermission, AdminSessionStore, AdminSite, AppException, AttemptThrottle, AuditAction, BaseAuditLogModel, BaseController, BaseModel, BaseOAuthClient, BaseOutboxModel, BaseService, BaseUserModel, BaseUserRefreshTokenModel, BaseUserTokenModel, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_DOCS_FAVICON, DEFAULT_LOCALE, EmailProvider, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, GitHubOAuthClient, GoogleOAuthClient, GracefulShutdown, HTTPClient, HTTP_500_LOG_FILE, HTTP_500_MARKER, HttpMetrics, IDEMPOTENCY_HEADER, InvalidTokenException, JSONLogger, JWTUtils, LEVEL_LOG_FILES, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemoryIdempotencyStore, MemoryRateLimitStore, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MessagingHub, MetricsUtils, MfaService, MultipartLimitError, NotFoundException, OAuthError, OIDCProvider, OutboxRelay, OutboxStatus, PHONE_BR_PATTERN, PasswordResetService, REDOC_CDN_URL, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, RedisIdempotencyStore, RedisRateLimitStore, RedisSSEBroker, RedisSessionStore, Region, RetryPolicy, S3UploadStorage, SSEBroker, ServerSentEvent, SessionService, TOTPHelper, TaskManager, TelegramProvider, TenantScopedRepository, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, UserModelAuthBackend, UserTokenPurpose, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WebhookSignatureVerifier, WhatsAppProvider, activationSchema, addLogSink, adminAction, adminColumns, adminInline, adminLens, adminThemeCss, attachWebSocketHub, authResponseSchema, authSettingsShape, backupDatabase, bearerToken, bodySizeLimitMiddleware, broadcastText, buildContentDisposition, buildFormFields, buildPaginationLinkHeader, cached2 as cached, cepField, citiesByUf, cnpjField, coerceFlag, configureFileLogging, configureLogging, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createTestDatabase, createdByColumn, csrfMiddleware, csrfTokenMatches, cursorPaginationFilterSchema, cursorPaginationSchema, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, diffSnapshots, emailSettingsShape, encodeCursor, envList, escapeHtml, filterForColumn, foreignKeyFields, foreignKeyLabel, foreignKeyTable, formatCellValue, formatFieldValue, generateCsrfToken, generateOAuthState, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, groupInlineSubmission, hashOpaqueToken, humanizeField, idempotencyMiddleware, inboundMessageSchema, isColumnOptional, isMultipart, isSearchableColumn, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, jwtSettingsShape, keyByHeader, keyByIp, keyByJwtClaim, keyByJwtSubject, listStates, logEntrySchema, logSettingsShape, loginSchema, makeAdminJsonRouter, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeLogsRouter, makeMetricsRouter, makeSessionMiddleware, makeToolSpecRouter, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, metricCard, mfaChallengeSchema, mfaCodeSchema, mfaEnrollResponseSchema, minioSettingsShape, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, parseCsv, parseFormBody, parseMultipart, partitionTotal, passwordResetConfirmSchema, passwordResetRequestSchema, phoneBrField, prometheusMiddleware, rabbitmqSettingsShape, rateLimitMiddleware, redisSettingsShape, refreshSchema, registerExceptionHandlers, renderAuthResultPage, renderDashboardPage, renderDetailPage, renderFormPage, renderImportPage, renderLayout, renderListPage, renderLoginPage, renderMfaPage, renderPasswordResetFormPage, requestIdMiddleware, requestTracingMiddleware, requireRoles, resolveAdminTheme, resolveDownloadPath, resolveRedocBundle, runServer, runWithRequestContext, sendBytesDownload, sendFileDownload, sessionCookie, sessionSettingsShape, setRequestId, signupSchema, snapshot, sseResponse, statesByRegion, syncFilterSchema, syncPaginationSchema, tableNameFor, toUtc, tokenFromUrl, tokenPairSchema, tokenSettingsShape, trendDirection, trendPercent, ufField, updatedByColumn, uploadSettingsShape, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSettingsShape, webPushSubscriptionSchema, webSocketSettingsShape, widgetForColumn, withTestDatabase, wrapWithSlowQueryLog, wsEnvelopeSchema };
|
|
15158
15974
|
//# sourceMappingURL=index.js.map
|
|
15159
15975
|
//# sourceMappingURL=index.js.map
|