@bobbykim/manguito-cms-api 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-FBS2RQI3.mjs +232 -0
- package/dist/chunk-FBS2RQI3.mjs.map +1 -0
- package/dist/chunk-QXMW74MR.mjs +589 -0
- package/dist/chunk-QXMW74MR.mjs.map +1 -0
- package/dist/graphql/index.d.mts +15 -0
- package/dist/graphql/index.d.ts +15 -0
- package/dist/graphql/index.js +729 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/index.mjs +8 -0
- package/dist/graphql/index.mjs.map +1 -0
- package/dist/handler-GMCFYZY5.mjs +8 -0
- package/dist/handler-GMCFYZY5.mjs.map +1 -0
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +1121 -258
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +349 -341
- package/dist/index.mjs.map +1 -1
- package/dist/resolve-Dtsk27aI.d.mts +15 -0
- package/dist/resolve-Dtsk27aI.d.ts +15 -0
- package/package.json +12 -3
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRelationsMap,
|
|
3
|
+
deleteParagraphField,
|
|
4
|
+
persistJunctionField,
|
|
5
|
+
persistParagraphField,
|
|
6
|
+
resolveRelationBareIds,
|
|
7
|
+
resolveRelationField
|
|
8
|
+
} from "./chunk-FBS2RQI3.mjs";
|
|
9
|
+
|
|
1
10
|
// src/app.ts
|
|
2
11
|
import { Hono } from "hono";
|
|
3
|
-
import { sql as
|
|
12
|
+
import { sql as sql9 } from "drizzle-orm";
|
|
4
13
|
|
|
5
14
|
// src/middleware/cors.ts
|
|
6
15
|
function createCorsMiddleware(corsConfig) {
|
|
@@ -31,26 +40,43 @@ function createCorsMiddleware(corsConfig) {
|
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
// src/middleware/security-headers.ts
|
|
43
|
+
var GRAPHIQL_CDN = "https://unpkg.com";
|
|
34
44
|
function createSecurityHeadersMiddleware(options = {}) {
|
|
35
45
|
const connectSrc = ["'self'", ...options.connectSrc ?? []].join(" ");
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
function buildCsp(parts) {
|
|
47
|
+
return [
|
|
48
|
+
"default-src 'self'",
|
|
49
|
+
"img-src 'self' data: https:",
|
|
50
|
+
"media-src 'self' https:",
|
|
51
|
+
`style-src ${parts.styleSrc}`,
|
|
52
|
+
`script-src ${parts.scriptSrc}`,
|
|
53
|
+
"font-src 'self' data:",
|
|
54
|
+
`connect-src ${parts.connectSrc}`,
|
|
55
|
+
...parts.workerSrc ? [`worker-src ${parts.workerSrc}`] : [],
|
|
56
|
+
"frame-ancestors 'none'",
|
|
57
|
+
"base-uri 'self'",
|
|
58
|
+
"form-action 'self'"
|
|
59
|
+
].join("; ");
|
|
60
|
+
}
|
|
61
|
+
const csp = buildCsp({
|
|
62
|
+
scriptSrc: "'self'",
|
|
63
|
+
styleSrc: "'self' 'unsafe-inline'",
|
|
64
|
+
connectSrc
|
|
65
|
+
});
|
|
66
|
+
const graphiqlCsp = buildCsp({
|
|
67
|
+
scriptSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,
|
|
68
|
+
styleSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,
|
|
69
|
+
connectSrc: `${connectSrc} ${GRAPHIQL_CDN}`,
|
|
70
|
+
workerSrc: "'self' blob:"
|
|
71
|
+
});
|
|
72
|
+
const graphiqlPath = options.graphiqlPath;
|
|
48
73
|
return async function securityHeaders(c, next) {
|
|
49
74
|
await next();
|
|
50
75
|
c.res.headers.set("X-Content-Type-Options", "nosniff");
|
|
51
76
|
c.res.headers.set("X-Frame-Options", "DENY");
|
|
52
77
|
c.res.headers.set("Referrer-Policy", "no-referrer");
|
|
53
|
-
c.
|
|
78
|
+
const isGraphiql = graphiqlPath !== void 0 && c.req.path === graphiqlPath;
|
|
79
|
+
c.res.headers.set("Content-Security-Policy", isGraphiql ? graphiqlCsp : csp);
|
|
54
80
|
};
|
|
55
81
|
}
|
|
56
82
|
|
|
@@ -425,6 +451,140 @@ function buildRolesRegistry(roles) {
|
|
|
425
451
|
return Object.fromEntries(roles.map((r) => [r.name, r]));
|
|
426
452
|
}
|
|
427
453
|
|
|
454
|
+
// src/programmatic/resolve.ts
|
|
455
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
456
|
+
var LIST_CONCURRENCY = 10;
|
|
457
|
+
function resolverKey(schema, field) {
|
|
458
|
+
return `${schema}::${field}`;
|
|
459
|
+
}
|
|
460
|
+
function validateResolverBindings(registry, resolvers) {
|
|
461
|
+
const declared = /* @__PURE__ */ new Set();
|
|
462
|
+
const schemas = [
|
|
463
|
+
...Object.values(registry.content_types),
|
|
464
|
+
...Object.values(registry.taxonomy_types)
|
|
465
|
+
];
|
|
466
|
+
for (const schema of schemas) {
|
|
467
|
+
for (const field of schema.fields) {
|
|
468
|
+
if (field.field_type === "programmatic") {
|
|
469
|
+
declared.add(resolverKey(schema.name, field.name));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const missing = [];
|
|
474
|
+
for (const key of declared) if (!resolvers.has(key)) missing.push(key);
|
|
475
|
+
const orphans = [];
|
|
476
|
+
for (const key of resolvers.keys()) if (!declared.has(key)) orphans.push(key);
|
|
477
|
+
if (missing.length === 0 && orphans.length === 0) return;
|
|
478
|
+
const lines = ["\u2717 Programmatic field resolver bindings are invalid."];
|
|
479
|
+
if (missing.length > 0) {
|
|
480
|
+
lines.push("", ' Declared as `type: "programmatic"` but no resolver found:');
|
|
481
|
+
for (const k of missing) lines.push(` - ${k} (add a programmaticField in src/programmatic)`);
|
|
482
|
+
}
|
|
483
|
+
if (orphans.length > 0) {
|
|
484
|
+
lines.push("", " Resolver has no matching programmatic field in the schema:");
|
|
485
|
+
for (const k of orphans) lines.push(` - ${k}`);
|
|
486
|
+
}
|
|
487
|
+
lines.push("", "Exiting.");
|
|
488
|
+
throw new Error(lines.join("\n"));
|
|
489
|
+
}
|
|
490
|
+
function buildContext(row) {
|
|
491
|
+
return {
|
|
492
|
+
get: (name) => row[name],
|
|
493
|
+
record: row
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
async function withTimeout(p, ms) {
|
|
497
|
+
let timer;
|
|
498
|
+
const timeout = new Promise((_, reject) => {
|
|
499
|
+
timer = setTimeout(() => reject(new Error("resolver timeout")), ms);
|
|
500
|
+
});
|
|
501
|
+
try {
|
|
502
|
+
return await Promise.race([p, timeout]);
|
|
503
|
+
} finally {
|
|
504
|
+
clearTimeout(timer);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function runOne(def, row, onError) {
|
|
508
|
+
const fallback = def.fallback ?? null;
|
|
509
|
+
try {
|
|
510
|
+
const result = await withTimeout(
|
|
511
|
+
Promise.resolve(def.resolve(buildContext(row))),
|
|
512
|
+
def.timeout ?? DEFAULT_TIMEOUT_MS
|
|
513
|
+
);
|
|
514
|
+
return { value: result === void 0 ? null : result, failed: false };
|
|
515
|
+
} catch (err) {
|
|
516
|
+
onError(
|
|
517
|
+
`\u26A0 programmatic field ${resolverKey(def.schema, def.field)} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
518
|
+
);
|
|
519
|
+
return { value: fallback, failed: true };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async function runPool(items, limit, fn) {
|
|
523
|
+
let i = 0;
|
|
524
|
+
async function worker() {
|
|
525
|
+
while (i < items.length) {
|
|
526
|
+
const idx = i++;
|
|
527
|
+
await fn(items[idx]);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, worker);
|
|
531
|
+
await Promise.all(workers);
|
|
532
|
+
}
|
|
533
|
+
function createProgrammaticResolver(resolvers, options) {
|
|
534
|
+
const onError = options?.onError ?? ((message) => {
|
|
535
|
+
process.stderr.write(message + "\n");
|
|
536
|
+
});
|
|
537
|
+
const bySchema = /* @__PURE__ */ new Map();
|
|
538
|
+
for (const def of resolvers.values()) {
|
|
539
|
+
const arr = bySchema.get(def.schema) ?? [];
|
|
540
|
+
arr.push(def);
|
|
541
|
+
bySchema.set(def.schema, arr);
|
|
542
|
+
}
|
|
543
|
+
const cache = /* @__PURE__ */ new Map();
|
|
544
|
+
async function resolveField(def, row) {
|
|
545
|
+
const itemId = row["id"] !== void 0 ? String(row["id"]) : "";
|
|
546
|
+
if (def.cache && itemId) {
|
|
547
|
+
const key = `${resolverKey(def.schema, def.field)}::${itemId}`;
|
|
548
|
+
const now = Date.now();
|
|
549
|
+
const hit = cache.get(key);
|
|
550
|
+
if (hit && hit.expires > now) return hit.value;
|
|
551
|
+
const { value: value2, failed } = await runOne(def, row, onError);
|
|
552
|
+
if (!failed) cache.set(key, { value: value2, expires: now + def.cache.ttl * 1e3 });
|
|
553
|
+
return value2;
|
|
554
|
+
}
|
|
555
|
+
const { value } = await runOne(def, row, onError);
|
|
556
|
+
return value;
|
|
557
|
+
}
|
|
558
|
+
async function resolveItem(schema, row) {
|
|
559
|
+
const defs = bySchema.get(schema);
|
|
560
|
+
if (!defs || defs.length === 0) return { ...row };
|
|
561
|
+
const out = { ...row };
|
|
562
|
+
await Promise.all(
|
|
563
|
+
defs.map(async (def) => {
|
|
564
|
+
out[def.field] = await resolveField(def, row);
|
|
565
|
+
})
|
|
566
|
+
);
|
|
567
|
+
return out;
|
|
568
|
+
}
|
|
569
|
+
async function resolveList(schema, rows) {
|
|
570
|
+
const defs = (bySchema.get(schema) ?? []).filter((d) => d.on_list === true);
|
|
571
|
+
if (defs.length === 0) return rows.map((r) => ({ ...r }));
|
|
572
|
+
const out = rows.map((r) => ({ ...r }));
|
|
573
|
+
const tasks = [];
|
|
574
|
+
for (let i = 0; i < rows.length; i++) {
|
|
575
|
+
for (const def of defs) tasks.push({ rowIndex: i, def });
|
|
576
|
+
}
|
|
577
|
+
await runPool(tasks, LIST_CONCURRENCY, async ({ rowIndex, def }) => {
|
|
578
|
+
out[rowIndex][def.field] = await resolveField(def, rows[rowIndex]);
|
|
579
|
+
});
|
|
580
|
+
return out;
|
|
581
|
+
}
|
|
582
|
+
function hasSchema(schema) {
|
|
583
|
+
return bySchema.has(schema);
|
|
584
|
+
}
|
|
585
|
+
return { resolveItem, resolveList, hasSchema };
|
|
586
|
+
}
|
|
587
|
+
|
|
428
588
|
// src/routes/query-params.ts
|
|
429
589
|
var SORTABLE_FIELDS = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
430
590
|
var RELATION_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -476,7 +636,7 @@ function parseFilters(url, validFields) {
|
|
|
476
636
|
function isPublished(item) {
|
|
477
637
|
return item["published"] === true;
|
|
478
638
|
}
|
|
479
|
-
function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
639
|
+
function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolver) {
|
|
480
640
|
function registerListRoute(path, handler) {
|
|
481
641
|
if (listRateLimit) {
|
|
482
642
|
app.get(path, listRateLimit, handler);
|
|
@@ -503,13 +663,13 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
503
663
|
const basePath = contentType.default_base_path;
|
|
504
664
|
const repo = repos[typeName];
|
|
505
665
|
if (!repo) continue;
|
|
506
|
-
const schemaFieldNames = /* @__PURE__ */ new Set([
|
|
507
|
-
...contentType.fields.map((f) => f.name),
|
|
508
|
-
...contentType.system_fields.map((f) => f.name)
|
|
509
|
-
]);
|
|
510
666
|
const relationFieldNames = new Set(
|
|
511
667
|
contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
|
|
512
668
|
);
|
|
669
|
+
const filterableFieldNames = /* @__PURE__ */ new Set([
|
|
670
|
+
...contentType.fields.filter((f) => f.field_type !== "programmatic").map((f) => f.name),
|
|
671
|
+
...contentType.system_fields.map((f) => f.name)
|
|
672
|
+
]);
|
|
513
673
|
if (contentType.only_one) {
|
|
514
674
|
app.get(`/api/${basePath}`, async (c) => {
|
|
515
675
|
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
@@ -519,7 +679,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
519
679
|
404
|
|
520
680
|
);
|
|
521
681
|
}
|
|
522
|
-
|
|
682
|
+
let data = result.data[0];
|
|
683
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
684
|
+
return c.json({ ok: true, data });
|
|
523
685
|
});
|
|
524
686
|
} else {
|
|
525
687
|
registerListRoute(`/api/${basePath}`, async (c) => {
|
|
@@ -562,7 +724,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
562
724
|
400
|
|
563
725
|
);
|
|
564
726
|
}
|
|
565
|
-
const filtersResult = parseFilters(c.req.url,
|
|
727
|
+
const filtersResult = parseFilters(c.req.url, filterableFieldNames);
|
|
566
728
|
if (!filtersResult.ok) {
|
|
567
729
|
return c.json(
|
|
568
730
|
{
|
|
@@ -599,6 +761,10 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
599
761
|
filters: filtersResult.filters,
|
|
600
762
|
include
|
|
601
763
|
});
|
|
764
|
+
if (resolver?.hasSchema(typeName)) {
|
|
765
|
+
const resolved = await resolver.resolveList(typeName, result.data);
|
|
766
|
+
return c.json({ ...result, data: resolved });
|
|
767
|
+
}
|
|
602
768
|
return c.json(result);
|
|
603
769
|
});
|
|
604
770
|
app.get(`/api/${basePath}/:slug`, async (c) => {
|
|
@@ -628,7 +794,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
628
794
|
404
|
|
629
795
|
);
|
|
630
796
|
}
|
|
631
|
-
|
|
797
|
+
let data = item;
|
|
798
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
799
|
+
return c.json({ ok: true, data });
|
|
632
800
|
});
|
|
633
801
|
}
|
|
634
802
|
}
|
|
@@ -654,6 +822,10 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
654
822
|
page: pagination.page,
|
|
655
823
|
per_page: pagination.per_page
|
|
656
824
|
});
|
|
825
|
+
if (resolver?.hasSchema(typeName)) {
|
|
826
|
+
const resolved = await resolver.resolveList(typeName, result.data);
|
|
827
|
+
return c.json({ ...result, data: resolved });
|
|
828
|
+
}
|
|
657
829
|
return c.json(result);
|
|
658
830
|
});
|
|
659
831
|
app.get(`/api/taxonomy/${typeName}/:id`, async (c) => {
|
|
@@ -668,7 +840,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
668
840
|
404
|
|
669
841
|
);
|
|
670
842
|
}
|
|
671
|
-
|
|
843
|
+
let data = item;
|
|
844
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
845
|
+
return c.json({ ok: true, data });
|
|
672
846
|
});
|
|
673
847
|
}
|
|
674
848
|
}
|
|
@@ -739,7 +913,7 @@ function registerPublicMediaRoutes(app, mediaRepo, listRateLimit) {
|
|
|
739
913
|
}
|
|
740
914
|
|
|
741
915
|
// src/routes/admin/content.ts
|
|
742
|
-
import { sql as
|
|
916
|
+
import { sql as sql2 } from "drizzle-orm";
|
|
743
917
|
|
|
744
918
|
// src/media-references.ts
|
|
745
919
|
function extractMediaId(value) {
|
|
@@ -773,235 +947,14 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
|
|
|
773
947
|
if (netRemove.length > 0) await mediaRepo.decrementReferenceCount(netRemove);
|
|
774
948
|
}
|
|
775
949
|
|
|
776
|
-
// src/relations.ts
|
|
777
|
-
import crypto from "crypto";
|
|
778
|
-
import { sql as sql2 } from "drizzle-orm";
|
|
779
|
-
function quoteIdent(name) {
|
|
780
|
-
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
781
|
-
return `"${name}"`;
|
|
782
|
-
}
|
|
783
|
-
function paragraphMediaFieldNames(pType) {
|
|
784
|
-
return new Set(
|
|
785
|
-
pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name)
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
789
|
-
const mediaColumns = pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.db_column.column_name);
|
|
790
|
-
if (mediaColumns.length === 0) return [];
|
|
791
|
-
const tbl = sql2.raw(quoteIdent(pType.db.table_name));
|
|
792
|
-
const cols = sql2.join(mediaColumns.map((c) => sql2.raw(quoteIdent(c))), sql2`, `);
|
|
793
|
-
const result = await db.execute(
|
|
794
|
-
sql2`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
795
|
-
);
|
|
796
|
-
const ids = [];
|
|
797
|
-
for (const row of result.rows) {
|
|
798
|
-
for (const col of mediaColumns) {
|
|
799
|
-
const v = row[col];
|
|
800
|
-
if (typeof v === "string" && v !== "") ids.push(v);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
return ids;
|
|
804
|
-
}
|
|
805
|
-
async function persistParagraphField(db, itemId, parentType, fieldName, pType, items) {
|
|
806
|
-
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
807
|
-
const tbl = sql2.raw(quoteIdent(pType.db.table_name));
|
|
808
|
-
await db.execute(sql2`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
809
|
-
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
810
|
-
const added = [];
|
|
811
|
-
for (let i = 0; i < items.length; i++) {
|
|
812
|
-
const pItem = items[i];
|
|
813
|
-
const data = {
|
|
814
|
-
id: crypto.randomUUID(),
|
|
815
|
-
parent_id: itemId,
|
|
816
|
-
parent_type: parentType,
|
|
817
|
-
parent_field: fieldName,
|
|
818
|
-
order: i,
|
|
819
|
-
created_at: /* @__PURE__ */ new Date(),
|
|
820
|
-
updated_at: /* @__PURE__ */ new Date()
|
|
821
|
-
};
|
|
822
|
-
for (const pf of pType.fields) {
|
|
823
|
-
if (!pf.db_column || pf.db_column.junction) continue;
|
|
824
|
-
data[pf.db_column.column_name] = pItem[pf.name] ?? null;
|
|
825
|
-
}
|
|
826
|
-
const cols = sql2.join(Object.keys(data).map((k) => sql2.raw(quoteIdent(k))), sql2`, `);
|
|
827
|
-
const vals = sql2.join(Object.values(data).map((v) => sql2`${v}`), sql2`, `);
|
|
828
|
-
await db.execute(sql2`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`);
|
|
829
|
-
for (const name of mediaFieldNames) {
|
|
830
|
-
const val = pItem[name];
|
|
831
|
-
if (typeof val === "string" && val !== "") added.push(val);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
return { added, removed };
|
|
835
|
-
}
|
|
836
|
-
async function deleteParagraphField(db, itemId, fieldName, pType) {
|
|
837
|
-
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
838
|
-
const tbl = sql2.raw(quoteIdent(pType.db.table_name));
|
|
839
|
-
await db.execute(sql2`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
840
|
-
return { added: [], removed };
|
|
841
|
-
}
|
|
842
|
-
async function persistJunctionField(db, itemId, junction, relatedIds) {
|
|
843
|
-
const tbl = sql2.raw(quoteIdent(junction.table_name));
|
|
844
|
-
const left = sql2.raw(quoteIdent(junction.left_column));
|
|
845
|
-
const right = sql2.raw(quoteIdent(junction.right_column));
|
|
846
|
-
await db.execute(sql2`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`);
|
|
847
|
-
for (const rid of relatedIds) {
|
|
848
|
-
await db.execute(sql2`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
function buildRelationsMap(fields, registry) {
|
|
852
|
-
const relations = {};
|
|
853
|
-
for (const field of fields) {
|
|
854
|
-
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
855
|
-
if (!field.db_column) continue;
|
|
856
|
-
relations[field.name] = { type: "media", fk_column: field.db_column.column_name };
|
|
857
|
-
} else if (field.field_type === "paragraph") {
|
|
858
|
-
const ref = field.ui_component.ref;
|
|
859
|
-
const pType = ref ? registry.paragraph_types[ref] : void 0;
|
|
860
|
-
if (!pType) continue;
|
|
861
|
-
relations[field.name] = { type: "paragraph", table: pType.db.table_name };
|
|
862
|
-
} else if (field.field_type === "reference") {
|
|
863
|
-
if (!field.db_column) continue;
|
|
864
|
-
if (field.db_column.junction) {
|
|
865
|
-
const j = field.db_column.junction;
|
|
866
|
-
relations[field.name] = {
|
|
867
|
-
type: "junction",
|
|
868
|
-
table: j.right_table,
|
|
869
|
-
junction_table: j.table_name,
|
|
870
|
-
left_column: j.left_column,
|
|
871
|
-
right_column: j.right_column,
|
|
872
|
-
order_column: j.order_column
|
|
873
|
-
};
|
|
874
|
-
} else if (field.db_column.foreign_key) {
|
|
875
|
-
relations[field.name] = {
|
|
876
|
-
type: "reference",
|
|
877
|
-
table: field.db_column.foreign_key.table,
|
|
878
|
-
fk_column: field.db_column.column_name
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
return relations;
|
|
884
|
-
}
|
|
885
|
-
function groupBy(items, key) {
|
|
886
|
-
const result = {};
|
|
887
|
-
for (const item of items) {
|
|
888
|
-
const k = String(item[key]);
|
|
889
|
-
if (!result[k]) result[k] = [];
|
|
890
|
-
result[k].push(item);
|
|
891
|
-
}
|
|
892
|
-
return result;
|
|
893
|
-
}
|
|
894
|
-
async function resolveRelationField(db, rows, fieldName, rel, cache) {
|
|
895
|
-
if (rows.length === 0) return;
|
|
896
|
-
if (rel.type === "paragraph") {
|
|
897
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
898
|
-
const inList = sql2.join(parentIds.map((id) => sql2`${id}`), sql2`, `);
|
|
899
|
-
const result = await db.execute(
|
|
900
|
-
sql2`SELECT * FROM ${sql2.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
901
|
-
);
|
|
902
|
-
const byParent = groupBy(result.rows, "parent_id");
|
|
903
|
-
for (const row of rows) {
|
|
904
|
-
row[fieldName] = byParent[row["id"]] ?? [];
|
|
905
|
-
}
|
|
906
|
-
} else if (rel.type === "reference") {
|
|
907
|
-
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
908
|
-
if (fkValues.length === 0) {
|
|
909
|
-
for (const row of rows) row[fieldName] = null;
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
const unique = [...new Set(fkValues)];
|
|
913
|
-
const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
914
|
-
if (uncached.length > 0) {
|
|
915
|
-
const inList = sql2.join(uncached.map((id) => sql2`${id}`), sql2`, `);
|
|
916
|
-
const result = await db.execute(
|
|
917
|
-
sql2`SELECT * FROM ${sql2.raw(quoteIdent(rel.table))} WHERE id IN (${inList})`
|
|
918
|
-
);
|
|
919
|
-
for (const item of result.rows) {
|
|
920
|
-
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
for (const row of rows) {
|
|
924
|
-
const fkVal = row[rel.fk_column];
|
|
925
|
-
row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
926
|
-
}
|
|
927
|
-
} else if (rel.type === "junction") {
|
|
928
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
929
|
-
const inList = sql2.join(parentIds.map((id) => sql2`${id}`), sql2`, `);
|
|
930
|
-
const orderBy = rel.order_column ? sql2` ORDER BY "order" ASC` : sql2``;
|
|
931
|
-
const junctionResult = await db.execute(
|
|
932
|
-
sql2`SELECT * FROM ${sql2.raw(quoteIdent(rel.junction_table))} WHERE ${sql2.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`
|
|
933
|
-
);
|
|
934
|
-
const jRows = junctionResult.rows;
|
|
935
|
-
const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
|
|
936
|
-
const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
937
|
-
if (uncached.length > 0) {
|
|
938
|
-
const rightInList = sql2.join(uncached.map((id) => sql2`${id}`), sql2`, `);
|
|
939
|
-
const entitiesResult = await db.execute(
|
|
940
|
-
sql2`SELECT * FROM ${sql2.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})`
|
|
941
|
-
);
|
|
942
|
-
for (const item of entitiesResult.rows) {
|
|
943
|
-
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
const byLeft = groupBy(jRows, rel.left_column);
|
|
947
|
-
for (const row of rows) {
|
|
948
|
-
const jEntries = byLeft[row["id"]] ?? [];
|
|
949
|
-
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
950
|
-
}
|
|
951
|
-
} else if (rel.type === "media") {
|
|
952
|
-
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
953
|
-
if (fkValues.length === 0) {
|
|
954
|
-
for (const row of rows) row[fieldName] = null;
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
const unique = [...new Set(fkValues)];
|
|
958
|
-
const uncached = unique.filter((id) => !cache.has(`media:${id}`));
|
|
959
|
-
if (uncached.length > 0) {
|
|
960
|
-
const inList = sql2.join(uncached.map((id) => sql2`${id}`), sql2`, `);
|
|
961
|
-
const result = await db.execute(sql2`SELECT * FROM "media" WHERE id IN (${inList})`);
|
|
962
|
-
for (const item of result.rows) {
|
|
963
|
-
cache.set(`media:${item["id"]}`, item);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
for (const row of rows) {
|
|
967
|
-
const fkVal = row[rel.fk_column];
|
|
968
|
-
row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
973
|
-
if (rows.length === 0) return;
|
|
974
|
-
if (rel.type === "paragraph") {
|
|
975
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
976
|
-
const inList = sql2.join(parentIds.map((id) => sql2`${id}`), sql2`, `);
|
|
977
|
-
const result = await db.execute(
|
|
978
|
-
sql2`SELECT id, parent_id FROM ${sql2.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
979
|
-
);
|
|
980
|
-
const byParent = groupBy(result.rows, "parent_id");
|
|
981
|
-
for (const row of rows) {
|
|
982
|
-
row[fieldName] = (byParent[row["id"]] ?? []).map((r) => r["id"]);
|
|
983
|
-
}
|
|
984
|
-
} else if (rel.type === "junction") {
|
|
985
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
986
|
-
const inList = sql2.join(parentIds.map((id) => sql2`${id}`), sql2`, `);
|
|
987
|
-
const result = await db.execute(
|
|
988
|
-
sql2`SELECT ${sql2.raw(quoteIdent(rel.left_column))} AS left_id, ${sql2.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql2.raw(quoteIdent(rel.junction_table))} WHERE ${sql2.raw(quoteIdent(rel.left_column))} IN (${inList})`
|
|
989
|
-
);
|
|
990
|
-
const byLeft = groupBy(result.rows, "left_id");
|
|
991
|
-
for (const row of rows) {
|
|
992
|
-
row[fieldName] = (byLeft[row["id"]] ?? []).map((r) => r["right_id"]);
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
950
|
// src/routes/admin/content.ts
|
|
998
|
-
function
|
|
951
|
+
function quoteIdent(name) {
|
|
999
952
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
1000
953
|
return `"${name}"`;
|
|
1001
954
|
}
|
|
1002
955
|
async function lookupBasePathId(db, pathOrName) {
|
|
1003
956
|
const r = await db.execute(
|
|
1004
|
-
|
|
957
|
+
sql2`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
|
|
1005
958
|
);
|
|
1006
959
|
return r.rows[0]?.id ?? null;
|
|
1007
960
|
}
|
|
@@ -1151,13 +1104,13 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1151
1104
|
const pType = registry.paragraph_types[comp.ref];
|
|
1152
1105
|
if (!pType) continue;
|
|
1153
1106
|
const r = await db.execute(
|
|
1154
|
-
|
|
1107
|
+
sql2`SELECT * FROM ${sql2.raw(quoteIdent(pType.db.table_name))} WHERE parent_id = ${id} AND parent_field = ${f.name} ORDER BY "order" ASC`
|
|
1155
1108
|
);
|
|
1156
1109
|
row[f.name] = r.rows;
|
|
1157
1110
|
} else if (f.db_column.junction) {
|
|
1158
1111
|
const j = f.db_column.junction;
|
|
1159
1112
|
const r = await db.execute(
|
|
1160
|
-
|
|
1113
|
+
sql2`SELECT ${sql2.raw(quoteIdent(j.right_column))} FROM ${sql2.raw(quoteIdent(j.table_name))} WHERE ${sql2.raw(quoteIdent(j.left_column))} = ${id}`
|
|
1161
1114
|
);
|
|
1162
1115
|
row[f.name] = r.rows.map(
|
|
1163
1116
|
(row2) => row2[j.right_column]
|
|
@@ -2122,7 +2075,7 @@ function registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, ma
|
|
|
2122
2075
|
|
|
2123
2076
|
// src/routes/admin/auth.ts
|
|
2124
2077
|
import { getCookie as getCookie2 } from "hono/cookie";
|
|
2125
|
-
import { sql as
|
|
2078
|
+
import { sql as sql3 } from "drizzle-orm";
|
|
2126
2079
|
|
|
2127
2080
|
// src/auth/password.ts
|
|
2128
2081
|
import { hashPassword, verifyPassword } from "@bobbykim/manguito-cms-core";
|
|
@@ -2188,7 +2141,7 @@ function registerAuthRoutes(app, db) {
|
|
|
2188
2141
|
);
|
|
2189
2142
|
}
|
|
2190
2143
|
const userResult = await db.execute(
|
|
2191
|
-
|
|
2144
|
+
sql3`SELECT id, email, password_hash, role_id, token_version
|
|
2192
2145
|
FROM users WHERE email = ${email} LIMIT 1`
|
|
2193
2146
|
);
|
|
2194
2147
|
const user = userResult.rows[0];
|
|
@@ -2201,7 +2154,7 @@ function registerAuthRoutes(app, db) {
|
|
|
2201
2154
|
return c.json(invalidCredentials, 401);
|
|
2202
2155
|
}
|
|
2203
2156
|
const roleResult = await db.execute(
|
|
2204
|
-
|
|
2157
|
+
sql3`SELECT r.name AS name, u.must_change_password
|
|
2205
2158
|
FROM roles r
|
|
2206
2159
|
JOIN users u ON u.role_id = r.id
|
|
2207
2160
|
WHERE r.id = ${user.role_id} AND u.id = ${user.id}
|
|
@@ -2261,7 +2214,7 @@ function registerAuthRoutes(app, db) {
|
|
|
2261
2214
|
);
|
|
2262
2215
|
}
|
|
2263
2216
|
const rowResult = await db.execute(
|
|
2264
|
-
|
|
2217
|
+
sql3`SELECT u.token_version, r.name AS role
|
|
2265
2218
|
FROM users u
|
|
2266
2219
|
JOIN roles r ON r.id = u.role_id
|
|
2267
2220
|
WHERE u.id = ${payload.user_id}
|
|
@@ -2285,7 +2238,7 @@ function registerAuthRoutes(app, db) {
|
|
|
2285
2238
|
app.post("/admin/api/auth/logout", authMiddleware, async (c) => {
|
|
2286
2239
|
const user = c.get("user");
|
|
2287
2240
|
await db.execute(
|
|
2288
|
-
|
|
2241
|
+
sql3`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
|
|
2289
2242
|
);
|
|
2290
2243
|
clearAuthCookie(c, "auth_token");
|
|
2291
2244
|
clearAuthCookie(c, "refresh_token", "/admin/api/auth");
|
|
@@ -2294,7 +2247,7 @@ function registerAuthRoutes(app, db) {
|
|
|
2294
2247
|
}
|
|
2295
2248
|
|
|
2296
2249
|
// src/routes/admin/users.ts
|
|
2297
|
-
import { sql as
|
|
2250
|
+
import { sql as sql4 } from "drizzle-orm";
|
|
2298
2251
|
import { randomBytes } from "crypto";
|
|
2299
2252
|
function getActingUser(c) {
|
|
2300
2253
|
return c.get("user");
|
|
@@ -2329,7 +2282,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2329
2282
|
);
|
|
2330
2283
|
}
|
|
2331
2284
|
const hashResult = await db.execute(
|
|
2332
|
-
|
|
2285
|
+
sql4`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
2333
2286
|
);
|
|
2334
2287
|
const hashRow = hashResult.rows[0];
|
|
2335
2288
|
if (!hashRow || !await verifyPassword(currentPassword, hashRow.password_hash)) {
|
|
@@ -2340,7 +2293,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2340
2293
|
}
|
|
2341
2294
|
const newHash = await hashPassword(newPassword);
|
|
2342
2295
|
await db.execute(
|
|
2343
|
-
|
|
2296
|
+
sql4`UPDATE users
|
|
2344
2297
|
SET password_hash = ${newHash},
|
|
2345
2298
|
must_change_password = false,
|
|
2346
2299
|
token_version = token_version + 1,
|
|
@@ -2351,7 +2304,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2351
2304
|
});
|
|
2352
2305
|
app.get("/admin/api/users", requirePermission("users:read"), async (c) => {
|
|
2353
2306
|
const result = await db.execute(
|
|
2354
|
-
|
|
2307
|
+
sql4`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
2355
2308
|
u.created_at, u.updated_at
|
|
2356
2309
|
FROM users u
|
|
2357
2310
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -2362,7 +2315,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2362
2315
|
app.get("/admin/api/users/:id", requirePermission("users:read"), async (c) => {
|
|
2363
2316
|
const id = c.req.param("id");
|
|
2364
2317
|
const result = await db.execute(
|
|
2365
|
-
|
|
2318
|
+
sql4`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
2366
2319
|
u.created_at, u.updated_at
|
|
2367
2320
|
FROM users u
|
|
2368
2321
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -2407,7 +2360,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2407
2360
|
);
|
|
2408
2361
|
}
|
|
2409
2362
|
const roleResult = await db.execute(
|
|
2410
|
-
|
|
2363
|
+
sql4`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
|
|
2411
2364
|
);
|
|
2412
2365
|
const roleRow = roleResult.rows[0];
|
|
2413
2366
|
if (!roleRow) {
|
|
@@ -2422,7 +2375,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2422
2375
|
const tempPassword = generateTempPassword();
|
|
2423
2376
|
const passwordHash = await hashPassword(tempPassword);
|
|
2424
2377
|
const insertResult = await db.execute(
|
|
2425
|
-
|
|
2378
|
+
sql4`INSERT INTO users
|
|
2426
2379
|
(id, email, password_hash, role_id, must_change_password, token_version,
|
|
2427
2380
|
created_at, updated_at)
|
|
2428
2381
|
VALUES
|
|
@@ -2477,7 +2430,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2477
2430
|
);
|
|
2478
2431
|
}
|
|
2479
2432
|
const existingResult = await db.execute(
|
|
2480
|
-
|
|
2433
|
+
sql4`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
|
|
2481
2434
|
);
|
|
2482
2435
|
const existing = existingResult.rows[0];
|
|
2483
2436
|
if (!existing) {
|
|
@@ -2491,7 +2444,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2491
2444
|
let changeRole = false;
|
|
2492
2445
|
if (typeof body.role === "string" && body.role) {
|
|
2493
2446
|
const roleResult = await db.execute(
|
|
2494
|
-
|
|
2447
|
+
sql4`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
|
|
2495
2448
|
);
|
|
2496
2449
|
const roleRow = roleResult.rows[0];
|
|
2497
2450
|
if (!roleRow) {
|
|
@@ -2511,7 +2464,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2511
2464
|
}
|
|
2512
2465
|
if (changeRole) {
|
|
2513
2466
|
await db.execute(
|
|
2514
|
-
|
|
2467
|
+
sql4`UPDATE users
|
|
2515
2468
|
SET email = ${newEmail},
|
|
2516
2469
|
role_id = ${newRoleId},
|
|
2517
2470
|
token_version = token_version + 1,
|
|
@@ -2520,7 +2473,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2520
2473
|
);
|
|
2521
2474
|
} else {
|
|
2522
2475
|
await db.execute(
|
|
2523
|
-
|
|
2476
|
+
sql4`UPDATE users
|
|
2524
2477
|
SET email = ${newEmail},
|
|
2525
2478
|
role_id = ${newRoleId},
|
|
2526
2479
|
updated_at = NOW()
|
|
@@ -2528,7 +2481,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2528
2481
|
);
|
|
2529
2482
|
}
|
|
2530
2483
|
const updatedResult = await db.execute(
|
|
2531
|
-
|
|
2484
|
+
sql4`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
2532
2485
|
u.created_at, u.updated_at
|
|
2533
2486
|
FROM users u
|
|
2534
2487
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -2565,7 +2518,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2565
2518
|
);
|
|
2566
2519
|
}
|
|
2567
2520
|
const result = await db.execute(
|
|
2568
|
-
|
|
2521
|
+
sql4`DELETE FROM users WHERE id = ${id} RETURNING id`
|
|
2569
2522
|
);
|
|
2570
2523
|
if (result.rows.length === 0) {
|
|
2571
2524
|
return c.json(
|
|
@@ -2596,7 +2549,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2596
2549
|
);
|
|
2597
2550
|
}
|
|
2598
2551
|
const existingResult = await db.execute(
|
|
2599
|
-
|
|
2552
|
+
sql4`SELECT id FROM users WHERE id = ${id} LIMIT 1`
|
|
2600
2553
|
);
|
|
2601
2554
|
const existing = existingResult.rows[0];
|
|
2602
2555
|
if (!existing) {
|
|
@@ -2608,7 +2561,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2608
2561
|
const tempPassword = generateTempPassword();
|
|
2609
2562
|
const passwordHash = await hashPassword(tempPassword);
|
|
2610
2563
|
await db.execute(
|
|
2611
|
-
|
|
2564
|
+
sql4`UPDATE users
|
|
2612
2565
|
SET password_hash = ${passwordHash},
|
|
2613
2566
|
must_change_password = true,
|
|
2614
2567
|
token_version = token_version + 1,
|
|
@@ -2621,10 +2574,10 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2621
2574
|
}
|
|
2622
2575
|
|
|
2623
2576
|
// src/routes/admin/config.ts
|
|
2624
|
-
import { sql as
|
|
2577
|
+
import { sql as sql5 } from "drizzle-orm";
|
|
2625
2578
|
var API_VERSION = (() => {
|
|
2626
2579
|
try {
|
|
2627
|
-
return "0.
|
|
2580
|
+
return "0.3.0";
|
|
2628
2581
|
} catch {
|
|
2629
2582
|
return "0.0.0";
|
|
2630
2583
|
}
|
|
@@ -2643,7 +2596,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
2643
2596
|
permissions: r.permissions
|
|
2644
2597
|
}));
|
|
2645
2598
|
const userResult = await db.execute(
|
|
2646
|
-
|
|
2599
|
+
sql5`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
2647
2600
|
);
|
|
2648
2601
|
const userRow = userResult.rows[0];
|
|
2649
2602
|
return c.json({
|
|
@@ -2669,7 +2622,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
2669
2622
|
}
|
|
2670
2623
|
|
|
2671
2624
|
// src/routes/admin/schema.ts
|
|
2672
|
-
import { sql as
|
|
2625
|
+
import { sql as sql6 } from "drizzle-orm";
|
|
2673
2626
|
function registerSchemaRoute(app, registry, db) {
|
|
2674
2627
|
app.get("/admin/api/schema", (c) => {
|
|
2675
2628
|
const content_types = Object.values(registry.content_types).map((ct) => ({
|
|
@@ -2706,7 +2659,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
2706
2659
|
const data = await Promise.all(
|
|
2707
2660
|
Object.values(registry.content_types).map(async (ct) => {
|
|
2708
2661
|
const result = await db.execute(
|
|
2709
|
-
|
|
2662
|
+
sql6`SELECT COUNT(*)::int AS count FROM ${sql6.raw(`"${ct.db.table_name}"`)}`
|
|
2710
2663
|
);
|
|
2711
2664
|
const count = Number(result.rows[0].count);
|
|
2712
2665
|
return { name: ct.name, label: ct.label, only_one: ct.only_one, count };
|
|
@@ -2718,7 +2671,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
2718
2671
|
const data = await Promise.all(
|
|
2719
2672
|
Object.values(registry.taxonomy_types).map(async (tt) => {
|
|
2720
2673
|
const result = await db.execute(
|
|
2721
|
-
|
|
2674
|
+
sql6`SELECT COUNT(*)::int AS count FROM ${sql6.raw(`"${tt.db.table_name}"`)}`
|
|
2722
2675
|
);
|
|
2723
2676
|
const count = Number(result.rows[0].count);
|
|
2724
2677
|
return { name: tt.name, label: tt.label, count };
|
|
@@ -2729,15 +2682,15 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
2729
2682
|
}
|
|
2730
2683
|
|
|
2731
2684
|
// src/repositories/content.ts
|
|
2732
|
-
import
|
|
2733
|
-
import { sql as
|
|
2685
|
+
import crypto from "crypto";
|
|
2686
|
+
import { sql as sql7 } from "drizzle-orm";
|
|
2734
2687
|
var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
2735
2688
|
function codeError(code, message) {
|
|
2736
2689
|
const err = new Error(message);
|
|
2737
2690
|
err.code = code;
|
|
2738
2691
|
return err;
|
|
2739
2692
|
}
|
|
2740
|
-
function
|
|
2693
|
+
function quoteIdent2(name) {
|
|
2741
2694
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
2742
2695
|
throw new Error(`Unsafe SQL identifier: ${name}`);
|
|
2743
2696
|
}
|
|
@@ -2752,43 +2705,43 @@ function quoteField(name) {
|
|
|
2752
2705
|
function buildConditions(published_only, filters, search) {
|
|
2753
2706
|
const conds = [];
|
|
2754
2707
|
if (published_only) {
|
|
2755
|
-
conds.push(
|
|
2708
|
+
conds.push(sql7`published = true`);
|
|
2756
2709
|
}
|
|
2757
2710
|
if (search && search.term.trim() !== "" && search.columns.length > 0) {
|
|
2758
2711
|
const pattern = `%${search.term.trim()}%`;
|
|
2759
|
-
const orConds = search.columns.map((col) =>
|
|
2760
|
-
conds.push(
|
|
2712
|
+
const orConds = search.columns.map((col) => sql7`${sql7.raw(quoteField(col))} ILIKE ${pattern}`);
|
|
2713
|
+
conds.push(sql7`(${sql7.join(orConds, sql7` OR `)})`);
|
|
2761
2714
|
}
|
|
2762
2715
|
if (filters) {
|
|
2763
2716
|
for (const [field, value] of Object.entries(filters)) {
|
|
2764
|
-
const col =
|
|
2717
|
+
const col = sql7.raw(quoteField(field));
|
|
2765
2718
|
if (Array.isArray(value)) {
|
|
2766
2719
|
if (value.length === 0) continue;
|
|
2767
|
-
const inList =
|
|
2768
|
-
value.map((v) =>
|
|
2769
|
-
|
|
2720
|
+
const inList = sql7.join(
|
|
2721
|
+
value.map((v) => sql7`${v}`),
|
|
2722
|
+
sql7`, `
|
|
2770
2723
|
);
|
|
2771
|
-
conds.push(
|
|
2724
|
+
conds.push(sql7`${col} IN (${inList})`);
|
|
2772
2725
|
} else if (typeof value === "object" && value !== null) {
|
|
2773
2726
|
const op = value;
|
|
2774
|
-
if (op.gt !== void 0) conds.push(
|
|
2775
|
-
if (op.gte !== void 0) conds.push(
|
|
2776
|
-
if (op.lt !== void 0) conds.push(
|
|
2777
|
-
if (op.lte !== void 0) conds.push(
|
|
2727
|
+
if (op.gt !== void 0) conds.push(sql7`${col} > ${op.gt}`);
|
|
2728
|
+
if (op.gte !== void 0) conds.push(sql7`${col} >= ${op.gte}`);
|
|
2729
|
+
if (op.lt !== void 0) conds.push(sql7`${col} < ${op.lt}`);
|
|
2730
|
+
if (op.lte !== void 0) conds.push(sql7`${col} <= ${op.lte}`);
|
|
2778
2731
|
} else {
|
|
2779
|
-
conds.push(
|
|
2732
|
+
conds.push(sql7`${col} = ${value}`);
|
|
2780
2733
|
}
|
|
2781
2734
|
}
|
|
2782
2735
|
}
|
|
2783
2736
|
return conds;
|
|
2784
2737
|
}
|
|
2785
2738
|
function whereFragment(conds) {
|
|
2786
|
-
return conds.length > 0 ?
|
|
2739
|
+
return conds.length > 0 ? sql7` WHERE ${sql7.join(conds, sql7` AND `)}` : sql7``;
|
|
2787
2740
|
}
|
|
2788
2741
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
2789
|
-
const { relations = {} } = options;
|
|
2742
|
+
const { relations = {}, publishedRelations = false } = options;
|
|
2790
2743
|
function tableRaw() {
|
|
2791
|
-
return
|
|
2744
|
+
return sql7.raw(quoteIdent2(tableName));
|
|
2792
2745
|
}
|
|
2793
2746
|
async function resolveRows(rows, include, cache) {
|
|
2794
2747
|
if (rows.length === 0) return rows;
|
|
@@ -2799,7 +2752,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2799
2752
|
}
|
|
2800
2753
|
for (const [fieldName, rel] of Object.entries(relations)) {
|
|
2801
2754
|
if (rel.type === "media") {
|
|
2802
|
-
await resolveRelationField(db, rows, fieldName, rel, cache);
|
|
2755
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations);
|
|
2803
2756
|
} else if (!include.includes(fieldName)) {
|
|
2804
2757
|
await resolveRelationBareIds(db, rows, fieldName, rel);
|
|
2805
2758
|
}
|
|
@@ -2807,7 +2760,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2807
2760
|
for (const fieldName of include) {
|
|
2808
2761
|
const rel = relations[fieldName];
|
|
2809
2762
|
if (rel.type !== "media") {
|
|
2810
|
-
await resolveRelationField(db, rows, fieldName, rel, cache);
|
|
2763
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations);
|
|
2811
2764
|
}
|
|
2812
2765
|
}
|
|
2813
2766
|
return rows;
|
|
@@ -2830,14 +2783,14 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2830
2783
|
const offset = (page - 1) * per_page;
|
|
2831
2784
|
const conds = buildConditions(published_only, filters, search);
|
|
2832
2785
|
const where = whereFragment(conds);
|
|
2833
|
-
const sortCol =
|
|
2834
|
-
const sortDir =
|
|
2786
|
+
const sortCol = sql7.raw(quoteIdent2(sort_by));
|
|
2787
|
+
const sortDir = sql7.raw(sort_order === "desc" ? "DESC" : "ASC");
|
|
2835
2788
|
const countResult = await db.execute(
|
|
2836
|
-
|
|
2789
|
+
sql7`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
|
|
2837
2790
|
);
|
|
2838
2791
|
const total = Number(countResult.rows[0]?.["total"] ?? 0);
|
|
2839
2792
|
const dataResult = await db.execute(
|
|
2840
|
-
|
|
2793
|
+
sql7`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
|
|
2841
2794
|
);
|
|
2842
2795
|
const rows = dataResult.rows;
|
|
2843
2796
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -2858,7 +2811,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2858
2811
|
},
|
|
2859
2812
|
async findOne(id, include = []) {
|
|
2860
2813
|
const result = await db.execute(
|
|
2861
|
-
|
|
2814
|
+
sql7`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
|
|
2862
2815
|
);
|
|
2863
2816
|
if (result.rows.length === 0) return null;
|
|
2864
2817
|
const rows = result.rows;
|
|
@@ -2868,7 +2821,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2868
2821
|
},
|
|
2869
2822
|
async findBySlug(slug, include = []) {
|
|
2870
2823
|
const result = await db.execute(
|
|
2871
|
-
|
|
2824
|
+
sql7`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
|
|
2872
2825
|
);
|
|
2873
2826
|
if (result.rows.length === 0) return null;
|
|
2874
2827
|
const rows = result.rows;
|
|
@@ -2879,15 +2832,15 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2879
2832
|
async create(data) {
|
|
2880
2833
|
const record = {
|
|
2881
2834
|
...data,
|
|
2882
|
-
id:
|
|
2835
|
+
id: crypto.randomUUID(),
|
|
2883
2836
|
created_at: /* @__PURE__ */ new Date(),
|
|
2884
2837
|
updated_at: /* @__PURE__ */ new Date()
|
|
2885
2838
|
};
|
|
2886
2839
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
2887
|
-
const colsSql =
|
|
2888
|
-
const valsSql =
|
|
2840
|
+
const colsSql = sql7.join(entries.map(([k]) => sql7.raw(quoteIdent2(k))), sql7`, `);
|
|
2841
|
+
const valsSql = sql7.join(entries.map(([, v]) => sql7`${v}`), sql7`, `);
|
|
2889
2842
|
const result = await db.execute(
|
|
2890
|
-
|
|
2843
|
+
sql7`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
2891
2844
|
);
|
|
2892
2845
|
return result.rows[0];
|
|
2893
2846
|
},
|
|
@@ -2900,25 +2853,25 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2900
2853
|
if (entries.length === 0) {
|
|
2901
2854
|
return this.findOne(id);
|
|
2902
2855
|
}
|
|
2903
|
-
const setClauses =
|
|
2904
|
-
entries.map(([k, v]) =>
|
|
2905
|
-
|
|
2856
|
+
const setClauses = sql7.join(
|
|
2857
|
+
entries.map(([k, v]) => sql7`${sql7.raw(quoteIdent2(k))} = ${v}`),
|
|
2858
|
+
sql7`, `
|
|
2906
2859
|
);
|
|
2907
2860
|
const result = await db.execute(
|
|
2908
|
-
|
|
2861
|
+
sql7`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
2909
2862
|
);
|
|
2910
2863
|
if (result.rows.length === 0) return null;
|
|
2911
2864
|
return result.rows[0];
|
|
2912
2865
|
},
|
|
2913
2866
|
async delete(id) {
|
|
2914
|
-
await db.execute(
|
|
2867
|
+
await db.execute(sql7`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
|
|
2915
2868
|
},
|
|
2916
2869
|
async findAll(opts = {}) {
|
|
2917
2870
|
const { include = [], published_only } = opts;
|
|
2918
2871
|
const conds = buildConditions(published_only, void 0, void 0);
|
|
2919
2872
|
const where = whereFragment(conds);
|
|
2920
2873
|
const result = await db.execute(
|
|
2921
|
-
|
|
2874
|
+
sql7`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
|
|
2922
2875
|
);
|
|
2923
2876
|
const rows = result.rows;
|
|
2924
2877
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -2929,24 +2882,24 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2929
2882
|
}
|
|
2930
2883
|
|
|
2931
2884
|
// src/repositories/media.ts
|
|
2932
|
-
import
|
|
2933
|
-
import { sql as
|
|
2885
|
+
import crypto2 from "crypto";
|
|
2886
|
+
import { sql as sql8 } from "drizzle-orm";
|
|
2934
2887
|
function createMediaRepository(db) {
|
|
2935
|
-
const TABLE =
|
|
2888
|
+
const TABLE = sql8.raw('"media"');
|
|
2936
2889
|
return {
|
|
2937
2890
|
async findMany(opts = {}) {
|
|
2938
2891
|
const { page = 1, per_page = 10, type, orphaned } = opts;
|
|
2939
2892
|
const offset = (page - 1) * per_page;
|
|
2940
2893
|
const conds = [];
|
|
2941
|
-
if (type) conds.push(
|
|
2942
|
-
if (orphaned) conds.push(
|
|
2943
|
-
const where = conds.length > 0 ?
|
|
2944
|
-
const countResult = await db.execute(
|
|
2894
|
+
if (type) conds.push(sql8`type = ${type}`);
|
|
2895
|
+
if (orphaned) conds.push(sql8`reference_count = 0`);
|
|
2896
|
+
const where = conds.length > 0 ? sql8` WHERE ${sql8.join(conds, sql8` AND `)}` : sql8``;
|
|
2897
|
+
const countResult = await db.execute(sql8`SELECT COUNT(*) AS total FROM ${TABLE}${where}`);
|
|
2945
2898
|
const total = Number(
|
|
2946
2899
|
countResult.rows[0]?.["total"] ?? 0
|
|
2947
2900
|
);
|
|
2948
2901
|
const dataResult = await db.execute(
|
|
2949
|
-
|
|
2902
|
+
sql8`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
|
|
2950
2903
|
);
|
|
2951
2904
|
const total_pages = Math.ceil(total / per_page);
|
|
2952
2905
|
return {
|
|
@@ -2964,7 +2917,7 @@ function createMediaRepository(db) {
|
|
|
2964
2917
|
},
|
|
2965
2918
|
async findOne(id) {
|
|
2966
2919
|
const result = await db.execute(
|
|
2967
|
-
|
|
2920
|
+
sql8`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
|
|
2968
2921
|
);
|
|
2969
2922
|
if (result.rows.length === 0) return null;
|
|
2970
2923
|
return result.rows[0];
|
|
@@ -2972,16 +2925,16 @@ function createMediaRepository(db) {
|
|
|
2972
2925
|
async create(data) {
|
|
2973
2926
|
const record = {
|
|
2974
2927
|
...data,
|
|
2975
|
-
id:
|
|
2928
|
+
id: crypto2.randomUUID(),
|
|
2976
2929
|
reference_count: 0,
|
|
2977
2930
|
created_at: /* @__PURE__ */ new Date(),
|
|
2978
2931
|
updated_at: /* @__PURE__ */ new Date()
|
|
2979
2932
|
};
|
|
2980
2933
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
2981
|
-
const colsSql =
|
|
2982
|
-
const valsSql =
|
|
2934
|
+
const colsSql = sql8.join(entries.map(([k]) => sql8.raw(`"${k}"`)), sql8`, `);
|
|
2935
|
+
const valsSql = sql8.join(entries.map(([, v]) => sql8`${v}`), sql8`, `);
|
|
2983
2936
|
const result = await db.execute(
|
|
2984
|
-
|
|
2937
|
+
sql8`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
2985
2938
|
);
|
|
2986
2939
|
return result.rows[0];
|
|
2987
2940
|
},
|
|
@@ -2991,31 +2944,31 @@ function createMediaRepository(db) {
|
|
|
2991
2944
|
([k, v]) => k !== "id" && v !== void 0
|
|
2992
2945
|
);
|
|
2993
2946
|
if (entries.length === 0) return this.findOne(id);
|
|
2994
|
-
const setClauses =
|
|
2995
|
-
entries.map(([k, v]) =>
|
|
2996
|
-
|
|
2947
|
+
const setClauses = sql8.join(
|
|
2948
|
+
entries.map(([k, v]) => sql8`${sql8.raw(`"${k}"`)} = ${v}`),
|
|
2949
|
+
sql8`, `
|
|
2997
2950
|
);
|
|
2998
2951
|
const result = await db.execute(
|
|
2999
|
-
|
|
2952
|
+
sql8`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
3000
2953
|
);
|
|
3001
2954
|
if (result.rows.length === 0) return null;
|
|
3002
2955
|
return result.rows[0];
|
|
3003
2956
|
},
|
|
3004
2957
|
async delete(id) {
|
|
3005
|
-
await db.execute(
|
|
2958
|
+
await db.execute(sql8`DELETE FROM ${TABLE} WHERE id = ${id}`);
|
|
3006
2959
|
},
|
|
3007
2960
|
async incrementReferenceCount(ids) {
|
|
3008
2961
|
if (ids.length === 0) return;
|
|
3009
|
-
const inList =
|
|
2962
|
+
const inList = sql8.join(ids.map((id) => sql8`${id}`), sql8`, `);
|
|
3010
2963
|
await db.execute(
|
|
3011
|
-
|
|
2964
|
+
sql8`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
|
|
3012
2965
|
);
|
|
3013
2966
|
},
|
|
3014
2967
|
async decrementReferenceCount(ids) {
|
|
3015
2968
|
if (ids.length === 0) return;
|
|
3016
|
-
const inList =
|
|
2969
|
+
const inList = sql8.join(ids.map((id) => sql8`${id}`), sql8`, `);
|
|
3017
2970
|
await db.execute(
|
|
3018
|
-
|
|
2971
|
+
sql8`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
|
|
3019
2972
|
);
|
|
3020
2973
|
}
|
|
3021
2974
|
};
|
|
@@ -3040,9 +2993,19 @@ function createCmsApp(options) {
|
|
|
3040
2993
|
const cmsName = options.name ?? "Manguito CMS";
|
|
3041
2994
|
const maxFileSize = media?.max_file_size;
|
|
3042
2995
|
const rolesRegistry = buildRolesRegistry(registry.roles.roles);
|
|
2996
|
+
const resolverMap = options.resolvers ?? /* @__PURE__ */ new Map();
|
|
2997
|
+
validateResolverBindings(registry, resolverMap);
|
|
2998
|
+
const programmaticResolver = createProgrammaticResolver(resolverMap);
|
|
3043
2999
|
const app = new Hono();
|
|
3044
3000
|
const uploadOrigins = storage.getUploadOrigins?.() ?? [];
|
|
3045
|
-
|
|
3001
|
+
const graphiqlEnabled = options.graphql?.enabled === true && options.graphql.graphiql === true;
|
|
3002
|
+
app.use(
|
|
3003
|
+
"*",
|
|
3004
|
+
createSecurityHeadersMiddleware({
|
|
3005
|
+
connectSrc: uploadOrigins,
|
|
3006
|
+
...graphiqlEnabled && { graphiqlPath: "/graphql" }
|
|
3007
|
+
})
|
|
3008
|
+
);
|
|
3046
3009
|
app.use("*", createCorsMiddleware(cors ?? { origin: "*", enabled: true }));
|
|
3047
3010
|
app.onError(errorHandler);
|
|
3048
3011
|
const listRateLimit = resolveListRateLimit(rateLimit);
|
|
@@ -3050,7 +3013,7 @@ function createCmsApp(options) {
|
|
|
3050
3013
|
const requirePermission = createPermissionMiddleware(rolesRegistry);
|
|
3051
3014
|
const getUserRole = async (userId) => {
|
|
3052
3015
|
const result = await db.execute(
|
|
3053
|
-
|
|
3016
|
+
sql9`SELECT r.name AS role
|
|
3054
3017
|
FROM users u
|
|
3055
3018
|
JOIN roles r ON r.id = u.role_id
|
|
3056
3019
|
WHERE u.id = ${userId}
|
|
@@ -3077,7 +3040,8 @@ function createCmsApp(options) {
|
|
|
3077
3040
|
Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3078
3041
|
typeName,
|
|
3079
3042
|
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
3080
|
-
relations: buildRelationsMap(ct.fields, registry)
|
|
3043
|
+
relations: buildRelationsMap(ct.fields, registry),
|
|
3044
|
+
publishedRelations: true
|
|
3081
3045
|
})
|
|
3082
3046
|
])
|
|
3083
3047
|
);
|
|
@@ -3085,7 +3049,8 @@ function createCmsApp(options) {
|
|
|
3085
3049
|
Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3086
3050
|
typeName,
|
|
3087
3051
|
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3088
|
-
relations: buildRelationsMap(tt.fields, registry)
|
|
3052
|
+
relations: buildRelationsMap(tt.fields, registry),
|
|
3053
|
+
publishedRelations: true
|
|
3089
3054
|
})
|
|
3090
3055
|
])
|
|
3091
3056
|
);
|
|
@@ -3124,8 +3089,37 @@ function createCmsApp(options) {
|
|
|
3124
3089
|
paths: {}
|
|
3125
3090
|
})
|
|
3126
3091
|
);
|
|
3127
|
-
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit);
|
|
3092
|
+
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit, programmaticResolver);
|
|
3128
3093
|
registerPublicMediaRoutes(app, mediaRepo, listRateLimit);
|
|
3094
|
+
if (options.graphql?.enabled) {
|
|
3095
|
+
const gqlOptions = options.graphql;
|
|
3096
|
+
let gqlHandler = null;
|
|
3097
|
+
let initError = null;
|
|
3098
|
+
const ready = import("./handler-GMCFYZY5.mjs").then(({ createGraphQLHandler }) => {
|
|
3099
|
+
gqlHandler = createGraphQLHandler(registry, publicRepos, programmaticResolver, db, gqlOptions);
|
|
3100
|
+
}).catch((err) => {
|
|
3101
|
+
initError = err;
|
|
3102
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3103
|
+
process.stderr.write(`\u2717 GraphQL schema failed to initialize; /graphql will return 500. ${message}
|
|
3104
|
+
`);
|
|
3105
|
+
});
|
|
3106
|
+
const invokeGraphQL = async (c) => {
|
|
3107
|
+
if (!gqlHandler && !initError) await ready;
|
|
3108
|
+
if (!gqlHandler) {
|
|
3109
|
+
return c.json(
|
|
3110
|
+
{ ok: false, error: { code: "GRAPHQL_INIT_FAILED", message: "GraphQL schema failed to initialize" } },
|
|
3111
|
+
500
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
return gqlHandler(c, async () => {
|
|
3115
|
+
});
|
|
3116
|
+
};
|
|
3117
|
+
if (listRateLimit) {
|
|
3118
|
+
app.all("/graphql", listRateLimit, invokeGraphQL);
|
|
3119
|
+
} else {
|
|
3120
|
+
app.all("/graphql", invokeGraphQL);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3129
3123
|
registerConfigRoute(
|
|
3130
3124
|
app,
|
|
3131
3125
|
{
|
|
@@ -3172,18 +3166,32 @@ function createServer(options = {}) {
|
|
|
3172
3166
|
}
|
|
3173
3167
|
|
|
3174
3168
|
// src/index.ts
|
|
3169
|
+
function resolveGraphQL(cfg) {
|
|
3170
|
+
const devDefault = process.env["NODE_ENV"] !== "production";
|
|
3171
|
+
return {
|
|
3172
|
+
enabled: cfg.enabled ?? false,
|
|
3173
|
+
maxDepth: cfg.maxDepth ?? 8,
|
|
3174
|
+
maxComplexity: cfg.maxComplexity ?? 1e3,
|
|
3175
|
+
graphiql: cfg.graphiql ?? devDefault,
|
|
3176
|
+
introspection: cfg.introspection ?? cfg.graphiql ?? devDefault
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3175
3179
|
function createAPIAdapter(options = {}) {
|
|
3176
3180
|
const prefix = options.prefix ?? "/api";
|
|
3177
3181
|
const media = options.media?.max_file_size !== void 0 ? { max_file_size: options.media.max_file_size } : void 0;
|
|
3178
3182
|
return {
|
|
3179
3183
|
prefix,
|
|
3180
3184
|
...media !== void 0 && { media },
|
|
3181
|
-
...options.rateLimit !== void 0 && { rateLimit: options.rateLimit }
|
|
3185
|
+
...options.rateLimit !== void 0 && { rateLimit: options.rateLimit },
|
|
3186
|
+
...options.graphql !== void 0 && { graphql: resolveGraphQL(options.graphql) }
|
|
3182
3187
|
};
|
|
3183
3188
|
}
|
|
3184
3189
|
export {
|
|
3185
3190
|
createAPIAdapter,
|
|
3186
3191
|
createCmsApp,
|
|
3187
|
-
|
|
3192
|
+
createProgrammaticResolver,
|
|
3193
|
+
createServer,
|
|
3194
|
+
resolverKey,
|
|
3195
|
+
validateResolverBindings
|
|
3188
3196
|
};
|
|
3189
3197
|
//# sourceMappingURL=index.mjs.map
|