@bobbykim/manguito-cms-api 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TK4DNRFF.mjs → chunk-4F2CRXPG.mjs} +76 -12
- package/dist/chunk-4F2CRXPG.mjs.map +1 -0
- package/dist/{chunk-GM55HGWA.mjs → chunk-S4NT6FUM.mjs} +39 -25
- package/dist/chunk-S4NT6FUM.mjs.map +1 -0
- package/dist/graphql/index.d.mts +14 -1
- package/dist/graphql/index.d.ts +14 -1
- package/dist/graphql/index.js +77 -33
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/index.mjs +2 -2
- package/dist/handler-7LVYEU5C.mjs +8 -0
- package/dist/index.js +348 -142
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +236 -111
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-GM55HGWA.mjs.map +0 -1
- package/dist/chunk-TK4DNRFF.mjs.map +0 -1
- package/dist/handler-CGHZG3J5.mjs +0 -8
- /package/dist/{handler-CGHZG3J5.mjs.map → handler-7LVYEU5C.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildRelationsMap,
|
|
3
|
+
createFieldKeyMap,
|
|
3
4
|
deleteParagraphField,
|
|
5
|
+
isColumnBacked,
|
|
4
6
|
persistJunctionField,
|
|
5
7
|
persistParagraphField,
|
|
6
8
|
resolveRelationBareIds,
|
|
7
9
|
resolveRelationField
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4F2CRXPG.mjs";
|
|
11
|
+
|
|
12
|
+
// src/paths.ts
|
|
13
|
+
function normalizePrefix(prefix) {
|
|
14
|
+
if (prefix === void 0) return "/api";
|
|
15
|
+
const withSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
16
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
17
|
+
return trimmed === "" ? "/api" : trimmed;
|
|
18
|
+
}
|
|
19
|
+
function createPublicPaths(prefix) {
|
|
20
|
+
return {
|
|
21
|
+
collection: (basePath) => `${prefix}/${basePath}`,
|
|
22
|
+
item: (basePath) => `${prefix}/${basePath}/:slug`,
|
|
23
|
+
taxonomyCollection: (typeName) => `${prefix}/taxonomy/${typeName}`,
|
|
24
|
+
taxonomyItem: (typeName) => `${prefix}/taxonomy/${typeName}/:id`,
|
|
25
|
+
mediaCollection: () => `${prefix}/media`,
|
|
26
|
+
mediaItem: () => `${prefix}/media/:id`,
|
|
27
|
+
openapi: () => `${prefix}/openapi.json`
|
|
28
|
+
};
|
|
29
|
+
}
|
|
9
30
|
|
|
10
31
|
// src/app.ts
|
|
11
32
|
import { Hono } from "hono";
|
|
@@ -585,6 +606,54 @@ function createProgrammaticResolver(resolvers, options) {
|
|
|
585
606
|
return { resolveItem, resolveList, hasSchema };
|
|
586
607
|
}
|
|
587
608
|
|
|
609
|
+
// src/projector.ts
|
|
610
|
+
function isPlainRow(v) {
|
|
611
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date);
|
|
612
|
+
}
|
|
613
|
+
var MEDIA_FIELD_TYPES = /* @__PURE__ */ new Set(["image", "video", "file"]);
|
|
614
|
+
function nestedTargets(fields) {
|
|
615
|
+
const out = [];
|
|
616
|
+
for (const f of fields) {
|
|
617
|
+
if (MEDIA_FIELD_TYPES.has(f.field_type)) continue;
|
|
618
|
+
if (f.field_type !== "paragraph" && f.field_type !== "reference") continue;
|
|
619
|
+
const ref = f.ui_component.ref;
|
|
620
|
+
if (!ref) continue;
|
|
621
|
+
out.push({ label: f.name, target: ref });
|
|
622
|
+
}
|
|
623
|
+
return out;
|
|
624
|
+
}
|
|
625
|
+
function buildProjectors(registry, fieldKeyMaps) {
|
|
626
|
+
const projectors = {};
|
|
627
|
+
const sources = [
|
|
628
|
+
registry.content_types,
|
|
629
|
+
registry.taxonomy_types,
|
|
630
|
+
registry.paragraph_types
|
|
631
|
+
];
|
|
632
|
+
for (const source of sources) {
|
|
633
|
+
for (const [typeName, type] of Object.entries(source)) {
|
|
634
|
+
const map = fieldKeyMaps[typeName];
|
|
635
|
+
if (!map) continue;
|
|
636
|
+
projectors[typeName] = { map, nested: nestedTargets(type.fields) };
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return projectors;
|
|
640
|
+
}
|
|
641
|
+
function projectRow(row, typeName, projectors) {
|
|
642
|
+
const p = projectors[typeName];
|
|
643
|
+
if (!p) return row;
|
|
644
|
+
const out = p.map.toLabels(row);
|
|
645
|
+
for (const { label, target } of p.nested) {
|
|
646
|
+
const v = out[label];
|
|
647
|
+
if (v === null || v === void 0) continue;
|
|
648
|
+
if (Array.isArray(v)) {
|
|
649
|
+
out[label] = v.map((item) => isPlainRow(item) ? projectRow(item, target, projectors) : item);
|
|
650
|
+
} else if (isPlainRow(v)) {
|
|
651
|
+
out[label] = projectRow(v, target, projectors);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
|
|
588
657
|
// src/routes/query-params.ts
|
|
589
658
|
var SORTABLE_FIELDS = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
590
659
|
var RELATION_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -605,28 +674,31 @@ function parseInclude(includeParam) {
|
|
|
605
674
|
if (!includeParam) return [];
|
|
606
675
|
return includeParam.split(",").map((s) => s.trim()).filter(Boolean);
|
|
607
676
|
}
|
|
608
|
-
function parseFilters(url, validFields) {
|
|
677
|
+
function parseFilters(url, validFields, columnFor) {
|
|
609
678
|
const { searchParams } = new URL(url);
|
|
610
679
|
const filters = {};
|
|
680
|
+
const toKey = (label) => columnFor?.(label) ?? label;
|
|
611
681
|
for (const [key, value] of searchParams.entries()) {
|
|
612
682
|
const simpleMatch = /^filter\[([^\]]+)\]$/.exec(key);
|
|
613
683
|
const opMatch = /^filter\[([^\]]+)\]\[([^\]]+)\]$/.exec(key);
|
|
614
684
|
if (simpleMatch) {
|
|
615
685
|
const field = simpleMatch[1];
|
|
616
686
|
if (!validFields.has(field)) return { ok: false, invalidField: field };
|
|
617
|
-
const
|
|
687
|
+
const storageKey = toKey(field);
|
|
688
|
+
const existing = filters[storageKey];
|
|
618
689
|
if (existing !== void 0) {
|
|
619
|
-
filters[
|
|
690
|
+
filters[storageKey] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
620
691
|
} else {
|
|
621
|
-
filters[
|
|
692
|
+
filters[storageKey] = value;
|
|
622
693
|
}
|
|
623
694
|
} else if (opMatch) {
|
|
624
695
|
const field = opMatch[1];
|
|
625
696
|
const operator = opMatch[2];
|
|
626
697
|
if (!validFields.has(field)) return { ok: false, invalidField: field };
|
|
627
698
|
if (!["gt", "gte", "lt", "lte"].includes(operator)) continue;
|
|
628
|
-
const
|
|
629
|
-
|
|
699
|
+
const storageKey = toKey(field);
|
|
700
|
+
const existing = filters[storageKey] ?? {};
|
|
701
|
+
filters[storageKey] = { ...existing, [operator]: value };
|
|
630
702
|
}
|
|
631
703
|
}
|
|
632
704
|
return { ok: true, filters };
|
|
@@ -636,7 +708,7 @@ function parseFilters(url, validFields) {
|
|
|
636
708
|
function isPublished(item) {
|
|
637
709
|
return item["published"] === true;
|
|
638
710
|
}
|
|
639
|
-
function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolver) {
|
|
711
|
+
function registerPublicContentRoutes(app, registry, repos, projectors, paths, listRateLimit, resolver) {
|
|
640
712
|
function registerListRoute(path, handler) {
|
|
641
713
|
if (listRateLimit) {
|
|
642
714
|
app.get(path, listRateLimit, handler);
|
|
@@ -644,7 +716,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
644
716
|
app.get(path, handler);
|
|
645
717
|
}
|
|
646
718
|
}
|
|
647
|
-
registerListRoute("
|
|
719
|
+
registerListRoute(paths.collection("content"), (c) => {
|
|
648
720
|
const data = Object.values(registry.content_types).map((ct) => ({
|
|
649
721
|
name: ct.name,
|
|
650
722
|
label: ct.label,
|
|
@@ -652,7 +724,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
652
724
|
}));
|
|
653
725
|
return c.json({ ok: true, data });
|
|
654
726
|
});
|
|
655
|
-
registerListRoute("
|
|
727
|
+
registerListRoute(paths.collection("taxonomy"), (c) => {
|
|
656
728
|
const data = Object.values(registry.taxonomy_types).map((tt) => ({
|
|
657
729
|
name: tt.name,
|
|
658
730
|
label: tt.label
|
|
@@ -671,7 +743,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
671
743
|
...contentType.system_fields.map((f) => f.name)
|
|
672
744
|
]);
|
|
673
745
|
if (contentType.only_one) {
|
|
674
|
-
app.get(
|
|
746
|
+
app.get(paths.collection(basePath), async (c) => {
|
|
675
747
|
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
676
748
|
if (result.data.length === 0) {
|
|
677
749
|
return c.json(
|
|
@@ -679,12 +751,14 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
679
751
|
404
|
|
680
752
|
);
|
|
681
753
|
}
|
|
682
|
-
let data = result.data[0];
|
|
754
|
+
let data = projectRow(result.data[0], typeName, projectors);
|
|
683
755
|
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
684
756
|
return c.json({ ok: true, data });
|
|
685
757
|
});
|
|
686
758
|
} else {
|
|
687
|
-
registerListRoute(
|
|
759
|
+
registerListRoute(paths.collection(basePath), async (c) => {
|
|
760
|
+
const projector = projectors[typeName];
|
|
761
|
+
const fieldKeys = projector.map;
|
|
688
762
|
const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
|
|
689
763
|
if (!pagination.ok) {
|
|
690
764
|
return c.json(
|
|
@@ -724,7 +798,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
724
798
|
400
|
|
725
799
|
);
|
|
726
800
|
}
|
|
727
|
-
const filtersResult = parseFilters(c.req.url, filterableFieldNames);
|
|
801
|
+
const filtersResult = parseFilters(c.req.url, filterableFieldNames, fieldKeys.columnFor);
|
|
728
802
|
if (!filtersResult.ok) {
|
|
729
803
|
return c.json(
|
|
730
804
|
{
|
|
@@ -752,22 +826,23 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
752
826
|
);
|
|
753
827
|
}
|
|
754
828
|
}
|
|
829
|
+
const sortColumn = fieldKeys.columnFor(sortBy) ?? sortBy;
|
|
755
830
|
const result = await repo.findMany({
|
|
756
831
|
published_only: true,
|
|
757
832
|
page: pagination.page,
|
|
758
833
|
per_page: pagination.per_page,
|
|
759
|
-
sort_by:
|
|
834
|
+
sort_by: sortColumn,
|
|
760
835
|
sort_order: sortOrder,
|
|
761
836
|
filters: filtersResult.filters,
|
|
762
837
|
include
|
|
763
838
|
});
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
return c.json(result);
|
|
839
|
+
const labeled = result.data.map(
|
|
840
|
+
(row) => projectRow(row, typeName, projectors)
|
|
841
|
+
);
|
|
842
|
+
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
843
|
+
return c.json({ ...result, data });
|
|
769
844
|
});
|
|
770
|
-
app.get(
|
|
845
|
+
app.get(paths.item(basePath), async (c) => {
|
|
771
846
|
const slug = c.req.param("slug");
|
|
772
847
|
const include = parseInclude(c.req.query("include"));
|
|
773
848
|
for (const field of include) {
|
|
@@ -794,7 +869,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
794
869
|
404
|
|
795
870
|
);
|
|
796
871
|
}
|
|
797
|
-
let data = item;
|
|
872
|
+
let data = projectRow(item, typeName, projectors);
|
|
798
873
|
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
799
874
|
return c.json({ ok: true, data });
|
|
800
875
|
});
|
|
@@ -803,7 +878,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
803
878
|
for (const [typeName] of Object.entries(registry.taxonomy_types)) {
|
|
804
879
|
const repo = repos[typeName];
|
|
805
880
|
if (!repo) continue;
|
|
806
|
-
registerListRoute(
|
|
881
|
+
registerListRoute(paths.taxonomyCollection(typeName), async (c) => {
|
|
807
882
|
const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
|
|
808
883
|
if (!pagination.ok) {
|
|
809
884
|
return c.json(
|
|
@@ -822,13 +897,13 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
822
897
|
page: pagination.page,
|
|
823
898
|
per_page: pagination.per_page
|
|
824
899
|
});
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return c.json(result);
|
|
900
|
+
const labeled = result.data.map(
|
|
901
|
+
(row) => projectRow(row, typeName, projectors)
|
|
902
|
+
);
|
|
903
|
+
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
904
|
+
return c.json({ ...result, data });
|
|
830
905
|
});
|
|
831
|
-
app.get(
|
|
906
|
+
app.get(paths.taxonomyItem(typeName), async (c) => {
|
|
832
907
|
const id = c.req.param("id");
|
|
833
908
|
const item = await repo.findOne(id);
|
|
834
909
|
if (!item || !isPublished(item)) {
|
|
@@ -840,7 +915,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolv
|
|
|
840
915
|
404
|
|
841
916
|
);
|
|
842
917
|
}
|
|
843
|
-
let data = item;
|
|
918
|
+
let data = projectRow(item, typeName, projectors);
|
|
844
919
|
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
845
920
|
return c.json({ ok: true, data });
|
|
846
921
|
});
|
|
@@ -856,7 +931,7 @@ function parsePagination2(pageStr, perPageStr) {
|
|
|
856
931
|
if (!Number.isInteger(per_page) || per_page < 1 || per_page > 100) return { ok: false };
|
|
857
932
|
return { ok: true, page, per_page };
|
|
858
933
|
}
|
|
859
|
-
function registerPublicMediaRoutes(app, mediaRepo, listRateLimit) {
|
|
934
|
+
function registerPublicMediaRoutes(app, mediaRepo, paths, listRateLimit) {
|
|
860
935
|
const mediaListHandler = async (c) => {
|
|
861
936
|
const pagination = parsePagination2(c.req.query("page"), c.req.query("per_page"));
|
|
862
937
|
if (!pagination.ok) {
|
|
@@ -895,11 +970,11 @@ function registerPublicMediaRoutes(app, mediaRepo, listRateLimit) {
|
|
|
895
970
|
return c.json(result);
|
|
896
971
|
};
|
|
897
972
|
if (listRateLimit) {
|
|
898
|
-
app.get(
|
|
973
|
+
app.get(paths.mediaCollection(), listRateLimit, mediaListHandler);
|
|
899
974
|
} else {
|
|
900
|
-
app.get(
|
|
975
|
+
app.get(paths.mediaCollection(), mediaListHandler);
|
|
901
976
|
}
|
|
902
|
-
app.get(
|
|
977
|
+
app.get(paths.mediaItem(), async (c) => {
|
|
903
978
|
const id = c.req.param("id");
|
|
904
979
|
const item = await mediaRepo.findOne(id);
|
|
905
980
|
if (!item) {
|
|
@@ -923,9 +998,11 @@ function topLevelMediaDelta(mediaFields, before, after) {
|
|
|
923
998
|
const added = [];
|
|
924
999
|
const removed = [];
|
|
925
1000
|
for (const f of mediaFields) {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1001
|
+
const key = f.db_column?.column_name;
|
|
1002
|
+
if (key === void 0 || key === "") continue;
|
|
1003
|
+
if (after !== null && !(key in after)) continue;
|
|
1004
|
+
const oldId = before ? extractMediaId(before[key]) : null;
|
|
1005
|
+
const newId = after ? extractMediaId(after[key]) : null;
|
|
929
1006
|
if (newId === oldId) continue;
|
|
930
1007
|
if (oldId) removed.push(oldId);
|
|
931
1008
|
if (newId) added.push(newId);
|
|
@@ -985,7 +1062,7 @@ function isEmpty(value) {
|
|
|
985
1062
|
function checkRequiredFields(fields, data) {
|
|
986
1063
|
return fields.filter((f) => f.required && isEmpty(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
|
|
987
1064
|
}
|
|
988
|
-
function parseListQuery(url, schemaFieldNames, relationFieldNames) {
|
|
1065
|
+
function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
|
|
989
1066
|
const searchParams = new URL(url).searchParams;
|
|
990
1067
|
const pagination = parsePagination(
|
|
991
1068
|
searchParams.get("page") ?? void 0,
|
|
@@ -1020,7 +1097,7 @@ function parseListQuery(url, schemaFieldNames, relationFieldNames) {
|
|
|
1020
1097
|
status: 400
|
|
1021
1098
|
};
|
|
1022
1099
|
}
|
|
1023
|
-
const filtersResult = parseFilters(url, schemaFieldNames);
|
|
1100
|
+
const filtersResult = parseFilters(url, schemaFieldNames, columnFor);
|
|
1024
1101
|
if (!filtersResult.ok) {
|
|
1025
1102
|
return {
|
|
1026
1103
|
ok: false,
|
|
@@ -1052,7 +1129,7 @@ function parseListQuery(url, schemaFieldNames, relationFieldNames) {
|
|
|
1052
1129
|
search
|
|
1053
1130
|
};
|
|
1054
1131
|
}
|
|
1055
|
-
function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePermission, db) {
|
|
1132
|
+
function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo, requirePermission, db) {
|
|
1056
1133
|
for (const [typeName, contentType] of Object.entries(registry.content_types)) {
|
|
1057
1134
|
const basePath = `content/${typeName}`;
|
|
1058
1135
|
const repo = repos[typeName];
|
|
@@ -1064,6 +1141,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1064
1141
|
const relationFieldNames = new Set(
|
|
1065
1142
|
contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
|
|
1066
1143
|
);
|
|
1144
|
+
const projector = projectors[typeName];
|
|
1145
|
+
const fieldKeys = projector.map;
|
|
1067
1146
|
const requiredFields = contentType.fields.filter((f) => f.required);
|
|
1068
1147
|
const mediaFields = contentType.fields.filter(
|
|
1069
1148
|
(f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
|
|
@@ -1077,17 +1156,18 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1077
1156
|
`/admin/api/${basePath}`,
|
|
1078
1157
|
requirePermission("content:read"),
|
|
1079
1158
|
async (c) => {
|
|
1080
|
-
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames);
|
|
1159
|
+
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor);
|
|
1081
1160
|
if (!parsed.ok) {
|
|
1082
1161
|
return c.json({ ok: false, error: parsed.response }, parsed.status);
|
|
1083
1162
|
}
|
|
1084
1163
|
const publishedParam = c.req.query("published");
|
|
1085
1164
|
const extraFilters = {};
|
|
1086
1165
|
if (publishedParam === "false") extraFilters["published"] = false;
|
|
1166
|
+
const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy;
|
|
1087
1167
|
const findOpts = {
|
|
1088
1168
|
page: parsed.pagination.page,
|
|
1089
1169
|
per_page: parsed.pagination.per_page,
|
|
1090
|
-
sort_by:
|
|
1170
|
+
sort_by: sortColumn,
|
|
1091
1171
|
sort_order: parsed.sortOrder,
|
|
1092
1172
|
filters: { ...parsed.filters, ...extraFilters },
|
|
1093
1173
|
include: parsed.include
|
|
@@ -1097,7 +1177,10 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1097
1177
|
findOpts.search = { term: parsed.search, columns: searchableColumns };
|
|
1098
1178
|
}
|
|
1099
1179
|
const result = await repo.findMany(findOpts);
|
|
1100
|
-
|
|
1180
|
+
const data = result.data.map(
|
|
1181
|
+
(row) => projectRow(row, typeName, projectors)
|
|
1182
|
+
);
|
|
1183
|
+
return c.json({ ...result, data });
|
|
1101
1184
|
}
|
|
1102
1185
|
);
|
|
1103
1186
|
app.get(
|
|
@@ -1132,7 +1215,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1132
1215
|
}
|
|
1133
1216
|
}
|
|
1134
1217
|
}
|
|
1135
|
-
|
|
1218
|
+
const data = projectRow(item, typeName, projectors);
|
|
1219
|
+
return c.json({ ok: true, data });
|
|
1136
1220
|
}
|
|
1137
1221
|
);
|
|
1138
1222
|
app.post(
|
|
@@ -1199,6 +1283,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1199
1283
|
}
|
|
1200
1284
|
);
|
|
1201
1285
|
const writeNewItem = async (c, body) => {
|
|
1286
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
1202
1287
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
1203
1288
|
if (fieldErrors.length > 0) {
|
|
1204
1289
|
return c.json(
|
|
@@ -1222,9 +1307,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1222
1307
|
const junctionFields = contentType.fields.filter(
|
|
1223
1308
|
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
1224
1309
|
);
|
|
1225
|
-
const
|
|
1226
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
1227
|
-
);
|
|
1310
|
+
const columnBackedFields = contentType.fields.filter(isColumnBacked);
|
|
1228
1311
|
const columnData = {
|
|
1229
1312
|
published: body["published"] ?? false
|
|
1230
1313
|
};
|
|
@@ -1233,9 +1316,10 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1233
1316
|
} else {
|
|
1234
1317
|
columnData["slug"] = body["slug"];
|
|
1235
1318
|
}
|
|
1236
|
-
for (const f of
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1319
|
+
for (const f of columnBackedFields) {
|
|
1320
|
+
const key = f.db_column.column_name;
|
|
1321
|
+
if (!(key in storageBody)) continue;
|
|
1322
|
+
columnData[key] = storageBody[key];
|
|
1239
1323
|
}
|
|
1240
1324
|
if (db) {
|
|
1241
1325
|
const basePathId = await lookupBasePathId(db, contentType.default_base_path);
|
|
@@ -1255,7 +1339,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1255
1339
|
}
|
|
1256
1340
|
const item = await repo.create(columnData);
|
|
1257
1341
|
const itemId = item["id"];
|
|
1258
|
-
const mediaDeltas = [topLevelMediaDelta(mediaFields, null,
|
|
1342
|
+
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
|
|
1259
1343
|
if (db) {
|
|
1260
1344
|
for (const f of paragraphFields) {
|
|
1261
1345
|
const comp = f.ui_component;
|
|
@@ -1272,7 +1356,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1272
1356
|
}
|
|
1273
1357
|
}
|
|
1274
1358
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
1275
|
-
|
|
1359
|
+
const data = projectRow(item, typeName, projectors);
|
|
1360
|
+
return c.json({ ok: true, data }, 201);
|
|
1276
1361
|
};
|
|
1277
1362
|
app.patch(
|
|
1278
1363
|
`/admin/api/${basePath}/:id`,
|
|
@@ -1332,11 +1417,12 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1332
1417
|
}
|
|
1333
1418
|
);
|
|
1334
1419
|
const writeExistingItem = async (c, id, existing, body) => {
|
|
1420
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
1335
1421
|
if (body["published"] === true) {
|
|
1336
1422
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
1337
1423
|
});
|
|
1338
1424
|
if (publishDeny) return publishDeny;
|
|
1339
|
-
const merged = { ...existing, ...body };
|
|
1425
|
+
const merged = { ...fieldKeys.toLabels(existing), ...body };
|
|
1340
1426
|
const fieldErrors = checkRequiredFields(requiredFields, merged);
|
|
1341
1427
|
if (fieldErrors.length > 0) {
|
|
1342
1428
|
return c.json(
|
|
@@ -1356,17 +1442,14 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1356
1442
|
const patchJunctionFields = contentType.fields.filter(
|
|
1357
1443
|
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
1358
1444
|
);
|
|
1359
|
-
const
|
|
1360
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
1361
|
-
);
|
|
1445
|
+
const patchColumnBackedFields = contentType.fields.filter(isColumnBacked);
|
|
1362
1446
|
const patchData = {};
|
|
1363
1447
|
if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
|
|
1364
1448
|
if ("published" in body) patchData["published"] = body["published"];
|
|
1365
|
-
for (const f of
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
}
|
|
1449
|
+
for (const f of patchColumnBackedFields) {
|
|
1450
|
+
const key = f.db_column.column_name;
|
|
1451
|
+
if (!(key in storageBody)) continue;
|
|
1452
|
+
patchData[key] = storageBody[key];
|
|
1370
1453
|
}
|
|
1371
1454
|
const updated = await repo.update(id, patchData);
|
|
1372
1455
|
if (!updated) {
|
|
@@ -1376,7 +1459,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1376
1459
|
);
|
|
1377
1460
|
}
|
|
1378
1461
|
const mediaDeltas = [
|
|
1379
|
-
topLevelMediaDelta(mediaFields, existing,
|
|
1462
|
+
topLevelMediaDelta(mediaFields, existing, storageBody)
|
|
1380
1463
|
];
|
|
1381
1464
|
if (db) {
|
|
1382
1465
|
for (const f of patchParagraphFields) {
|
|
@@ -1394,7 +1477,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1394
1477
|
}
|
|
1395
1478
|
}
|
|
1396
1479
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
1397
|
-
|
|
1480
|
+
const data = projectRow(updated, typeName, projectors);
|
|
1481
|
+
return c.json({ ok: true, data });
|
|
1398
1482
|
};
|
|
1399
1483
|
if (contentType.only_one) {
|
|
1400
1484
|
app.put(
|
|
@@ -1454,6 +1538,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1454
1538
|
const relationFieldNames = new Set(
|
|
1455
1539
|
taxonomyType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
|
|
1456
1540
|
);
|
|
1541
|
+
const projector = projectors[typeName];
|
|
1542
|
+
const fieldKeys = projector.map;
|
|
1457
1543
|
const requiredFields = taxonomyType.fields.filter((f) => f.required);
|
|
1458
1544
|
const mediaFields = taxonomyType.fields.filter(
|
|
1459
1545
|
(f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
|
|
@@ -1467,17 +1553,18 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1467
1553
|
`/admin/api/taxonomy/${typeName}`,
|
|
1468
1554
|
requirePermission("content:read"),
|
|
1469
1555
|
async (c) => {
|
|
1470
|
-
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames);
|
|
1556
|
+
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor);
|
|
1471
1557
|
if (!parsed.ok) {
|
|
1472
1558
|
return c.json({ ok: false, error: parsed.response }, parsed.status);
|
|
1473
1559
|
}
|
|
1474
1560
|
const publishedParam = c.req.query("published");
|
|
1475
1561
|
const extraFilters = {};
|
|
1476
1562
|
if (publishedParam === "false") extraFilters["published"] = false;
|
|
1563
|
+
const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy;
|
|
1477
1564
|
const findOpts = {
|
|
1478
1565
|
page: parsed.pagination.page,
|
|
1479
1566
|
per_page: parsed.pagination.per_page,
|
|
1480
|
-
sort_by:
|
|
1567
|
+
sort_by: sortColumn,
|
|
1481
1568
|
sort_order: parsed.sortOrder,
|
|
1482
1569
|
filters: { ...parsed.filters, ...extraFilters },
|
|
1483
1570
|
include: parsed.include
|
|
@@ -1487,7 +1574,10 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1487
1574
|
findOpts.search = { term: parsed.search, columns: searchableColumns };
|
|
1488
1575
|
}
|
|
1489
1576
|
const result = await repo.findMany(findOpts);
|
|
1490
|
-
|
|
1577
|
+
const data = result.data.map(
|
|
1578
|
+
(row) => projectRow(row, typeName, projectors)
|
|
1579
|
+
);
|
|
1580
|
+
return c.json({ ...result, data });
|
|
1491
1581
|
}
|
|
1492
1582
|
);
|
|
1493
1583
|
app.get(
|
|
@@ -1502,7 +1592,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1502
1592
|
404
|
|
1503
1593
|
);
|
|
1504
1594
|
}
|
|
1505
|
-
|
|
1595
|
+
const data = projectRow(item, typeName, projectors);
|
|
1596
|
+
return c.json({ ok: true, data });
|
|
1506
1597
|
}
|
|
1507
1598
|
);
|
|
1508
1599
|
app.post(
|
|
@@ -1510,6 +1601,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1510
1601
|
requirePermission("content:create"),
|
|
1511
1602
|
async (c) => {
|
|
1512
1603
|
const body = await c.req.json();
|
|
1604
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
1513
1605
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
1514
1606
|
if (fieldErrors.length > 0) {
|
|
1515
1607
|
return c.json(
|
|
@@ -1529,20 +1621,19 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1529
1621
|
});
|
|
1530
1622
|
if (publishDeny) return publishDeny;
|
|
1531
1623
|
}
|
|
1532
|
-
const taxColumnFields = taxonomyType.fields.filter(
|
|
1533
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
1534
|
-
);
|
|
1535
1624
|
const taxParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
|
|
1625
|
+
const taxColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
|
|
1536
1626
|
const taxColumnData = {
|
|
1537
1627
|
published: body["published"] ?? false
|
|
1538
1628
|
};
|
|
1539
|
-
for (const f of
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1629
|
+
for (const f of taxColumnBackedFields) {
|
|
1630
|
+
const key = f.db_column.column_name;
|
|
1631
|
+
if (!(key in storageBody)) continue;
|
|
1632
|
+
taxColumnData[key] = storageBody[key];
|
|
1542
1633
|
}
|
|
1543
1634
|
const item = await repo.create(taxColumnData);
|
|
1544
1635
|
const taxItemId = item["id"];
|
|
1545
|
-
const mediaDeltas = [topLevelMediaDelta(mediaFields, null,
|
|
1636
|
+
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
|
|
1546
1637
|
if (db) {
|
|
1547
1638
|
for (const f of taxParagraphFields) {
|
|
1548
1639
|
const comp = f.ui_component;
|
|
@@ -1554,7 +1645,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1554
1645
|
}
|
|
1555
1646
|
}
|
|
1556
1647
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
1557
|
-
|
|
1648
|
+
const data = projectRow(item, typeName, projectors);
|
|
1649
|
+
return c.json({ ok: true, data }, 201);
|
|
1558
1650
|
}
|
|
1559
1651
|
);
|
|
1560
1652
|
app.patch(
|
|
@@ -1563,6 +1655,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1563
1655
|
async (c) => {
|
|
1564
1656
|
const id = c.req.param("id");
|
|
1565
1657
|
const body = await c.req.json();
|
|
1658
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
1566
1659
|
const existing = await repo.findOne(id);
|
|
1567
1660
|
if (!existing) {
|
|
1568
1661
|
return c.json(
|
|
@@ -1574,7 +1667,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1574
1667
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
1575
1668
|
});
|
|
1576
1669
|
if (publishDeny) return publishDeny;
|
|
1577
|
-
const merged = { ...existing, ...body };
|
|
1670
|
+
const merged = { ...fieldKeys.toLabels(existing), ...body };
|
|
1578
1671
|
const fieldErrors = checkRequiredFields(requiredFields, merged);
|
|
1579
1672
|
if (fieldErrors.length > 0) {
|
|
1580
1673
|
return c.json(
|
|
@@ -1590,17 +1683,14 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1590
1683
|
);
|
|
1591
1684
|
}
|
|
1592
1685
|
}
|
|
1593
|
-
const taxPatchColumnFields = taxonomyType.fields.filter(
|
|
1594
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
1595
|
-
);
|
|
1596
1686
|
const taxPatchParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
|
|
1687
|
+
const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
|
|
1597
1688
|
const taxPatchData = {};
|
|
1598
1689
|
if ("published" in body) taxPatchData["published"] = body["published"];
|
|
1599
|
-
for (const f of
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
}
|
|
1690
|
+
for (const f of taxPatchColumnBackedFields) {
|
|
1691
|
+
const key = f.db_column.column_name;
|
|
1692
|
+
if (!(key in storageBody)) continue;
|
|
1693
|
+
taxPatchData[key] = storageBody[key];
|
|
1604
1694
|
}
|
|
1605
1695
|
const updated = await repo.update(id, taxPatchData);
|
|
1606
1696
|
if (!updated) {
|
|
@@ -1610,7 +1700,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1610
1700
|
);
|
|
1611
1701
|
}
|
|
1612
1702
|
const mediaDeltas = [
|
|
1613
|
-
topLevelMediaDelta(mediaFields, existing,
|
|
1703
|
+
topLevelMediaDelta(mediaFields, existing, storageBody)
|
|
1614
1704
|
];
|
|
1615
1705
|
if (db) {
|
|
1616
1706
|
for (const f of taxPatchParagraphFields) {
|
|
@@ -1623,7 +1713,8 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1623
1713
|
}
|
|
1624
1714
|
}
|
|
1625
1715
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
1626
|
-
|
|
1716
|
+
const data = projectRow(updated, typeName, projectors);
|
|
1717
|
+
return c.json({ ok: true, data });
|
|
1627
1718
|
}
|
|
1628
1719
|
);
|
|
1629
1720
|
app.delete(
|
|
@@ -2615,7 +2706,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2615
2706
|
import { sql as sql5 } from "drizzle-orm";
|
|
2616
2707
|
var API_VERSION = (() => {
|
|
2617
2708
|
try {
|
|
2618
|
-
return "0.
|
|
2709
|
+
return "0.4.1";
|
|
2619
2710
|
} catch {
|
|
2620
2711
|
return "0.0.0";
|
|
2621
2712
|
}
|
|
@@ -2777,7 +2868,7 @@ function whereFragment(conds) {
|
|
|
2777
2868
|
return conds.length > 0 ? sql7` WHERE ${sql7.join(conds, sql7` AND `)}` : sql7``;
|
|
2778
2869
|
}
|
|
2779
2870
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
2780
|
-
const { relations = {}, publishedRelations = false } = options;
|
|
2871
|
+
const { relations = {}, publishedRelations = false, sortableColumns } = options;
|
|
2781
2872
|
function tableRaw() {
|
|
2782
2873
|
return sql7.raw(quoteIdent2(tableName));
|
|
2783
2874
|
}
|
|
@@ -2815,8 +2906,9 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2815
2906
|
sort_order = "asc",
|
|
2816
2907
|
search
|
|
2817
2908
|
} = opts;
|
|
2818
|
-
|
|
2819
|
-
|
|
2909
|
+
const allowed = sortableColumns ?? SORTABLE_FIELDS2;
|
|
2910
|
+
if (!allowed.has(sort_by)) {
|
|
2911
|
+
throw codeError("INVALID_SORT_FIELD", `'${sort_by}' is not sortable.`);
|
|
2820
2912
|
}
|
|
2821
2913
|
const offset = (page - 1) * per_page;
|
|
2822
2914
|
const conds = buildConditions(published_only, filters, search);
|
|
@@ -3026,7 +3118,8 @@ function createCmsApp(options) {
|
|
|
3026
3118
|
if (!options.storage) {
|
|
3027
3119
|
throw new Error(MISSING_STORAGE_ERROR);
|
|
3028
3120
|
}
|
|
3029
|
-
const prefix = options.prefix
|
|
3121
|
+
const prefix = normalizePrefix(options.prefix);
|
|
3122
|
+
const publicPaths = createPublicPaths(prefix);
|
|
3030
3123
|
const { storage, registry, db, rateLimit, media, cors } = options;
|
|
3031
3124
|
const cmsName = options.name ?? "Manguito CMS";
|
|
3032
3125
|
const maxFileSize = media?.max_file_size;
|
|
@@ -3060,16 +3153,38 @@ function createCmsApp(options) {
|
|
|
3060
3153
|
return result.rows[0]?.role ?? null;
|
|
3061
3154
|
};
|
|
3062
3155
|
const requireHierarchy = createHierarchyMiddleware(rolesRegistry, getUserRole);
|
|
3156
|
+
const fieldKeyMaps = Object.fromEntries([
|
|
3157
|
+
...Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3158
|
+
typeName,
|
|
3159
|
+
createFieldKeyMap(ct.fields)
|
|
3160
|
+
]),
|
|
3161
|
+
...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3162
|
+
typeName,
|
|
3163
|
+
createFieldKeyMap(tt.fields)
|
|
3164
|
+
]),
|
|
3165
|
+
...Object.entries(registry.paragraph_types).map(([typeName, pt]) => [
|
|
3166
|
+
typeName,
|
|
3167
|
+
createFieldKeyMap(pt.fields)
|
|
3168
|
+
])
|
|
3169
|
+
]);
|
|
3170
|
+
const projectors = buildProjectors(registry, fieldKeyMaps);
|
|
3171
|
+
const sortableColumnsFor = (typeName) => new Set(
|
|
3172
|
+
[...SORTABLE_FIELDS].map((label) => fieldKeyMaps[typeName].columnFor(label) ?? label)
|
|
3173
|
+
);
|
|
3063
3174
|
const contentRepos = Object.fromEntries(
|
|
3064
3175
|
Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3065
3176
|
typeName,
|
|
3066
|
-
createDrizzleContentRepository(db, ct.db.table_name
|
|
3177
|
+
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
3178
|
+
sortableColumns: sortableColumnsFor(typeName)
|
|
3179
|
+
})
|
|
3067
3180
|
])
|
|
3068
3181
|
);
|
|
3069
3182
|
const taxonomyRepos = Object.fromEntries(
|
|
3070
3183
|
Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3071
3184
|
typeName,
|
|
3072
|
-
createDrizzleContentRepository(db, tt.db.table_name
|
|
3185
|
+
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3186
|
+
sortableColumns: sortableColumnsFor(typeName)
|
|
3187
|
+
})
|
|
3073
3188
|
])
|
|
3074
3189
|
);
|
|
3075
3190
|
const repos = { ...contentRepos, ...taxonomyRepos };
|
|
@@ -3079,7 +3194,8 @@ function createCmsApp(options) {
|
|
|
3079
3194
|
typeName,
|
|
3080
3195
|
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
3081
3196
|
relations: buildRelationsMap(ct.fields, registry),
|
|
3082
|
-
publishedRelations: true
|
|
3197
|
+
publishedRelations: true,
|
|
3198
|
+
sortableColumns: sortableColumnsFor(typeName)
|
|
3083
3199
|
})
|
|
3084
3200
|
])
|
|
3085
3201
|
);
|
|
@@ -3088,7 +3204,8 @@ function createCmsApp(options) {
|
|
|
3088
3204
|
typeName,
|
|
3089
3205
|
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3090
3206
|
relations: buildRelationsMap(tt.fields, registry),
|
|
3091
|
-
publishedRelations: true
|
|
3207
|
+
publishedRelations: true,
|
|
3208
|
+
sortableColumns: sortableColumnsFor(typeName)
|
|
3092
3209
|
})
|
|
3093
3210
|
])
|
|
3094
3211
|
);
|
|
@@ -3096,33 +3213,41 @@ function createCmsApp(options) {
|
|
|
3096
3213
|
const graphqlRepos = Object.fromEntries([
|
|
3097
3214
|
...Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3098
3215
|
typeName,
|
|
3099
|
-
createDrizzleContentRepository(db, ct.db.table_name
|
|
3216
|
+
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
3217
|
+
sortableColumns: sortableColumnsFor(typeName)
|
|
3218
|
+
})
|
|
3100
3219
|
]),
|
|
3101
3220
|
...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3102
3221
|
typeName,
|
|
3103
|
-
createDrizzleContentRepository(db, tt.db.table_name
|
|
3222
|
+
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3223
|
+
sortableColumns: sortableColumnsFor(typeName)
|
|
3224
|
+
})
|
|
3104
3225
|
])
|
|
3105
3226
|
]);
|
|
3106
3227
|
registerAuthRoutes(app, db);
|
|
3107
3228
|
app.use("/admin/api/*", authMiddleware);
|
|
3108
3229
|
app.use("/admin/api/*", mustChangePasswordCheck);
|
|
3109
|
-
app.get(
|
|
3230
|
+
app.get(publicPaths.openapi(), (c) => {
|
|
3110
3231
|
const paths = {};
|
|
3111
3232
|
for (const ct of Object.values(registry.content_types)) {
|
|
3112
3233
|
const base = ct.default_base_path;
|
|
3113
3234
|
if (ct.only_one) {
|
|
3114
|
-
paths[
|
|
3235
|
+
paths[publicPaths.collection(base)] = { get: { summary: `Get ${ct.label}`, tags: [ct.label] } };
|
|
3115
3236
|
} else {
|
|
3116
|
-
paths[
|
|
3117
|
-
paths[
|
|
3237
|
+
paths[publicPaths.collection(base)] = { get: { summary: `List published ${ct.label}`, tags: [ct.label] } };
|
|
3238
|
+
paths[publicPaths.item(base).replace(":slug", "{slug}")] = {
|
|
3239
|
+
get: { summary: `Get ${ct.label} by slug`, tags: [ct.label] }
|
|
3240
|
+
};
|
|
3118
3241
|
}
|
|
3119
3242
|
}
|
|
3120
3243
|
for (const tt of Object.values(registry.taxonomy_types)) {
|
|
3121
|
-
paths[
|
|
3122
|
-
paths[
|
|
3244
|
+
paths[publicPaths.taxonomyCollection(tt.name)] = { get: { summary: `List published ${tt.label}`, tags: [tt.label] } };
|
|
3245
|
+
paths[publicPaths.taxonomyItem(tt.name).replace(":id", "{id}")] = {
|
|
3246
|
+
get: { summary: `Get ${tt.label} by id`, tags: [tt.label] }
|
|
3247
|
+
};
|
|
3123
3248
|
}
|
|
3124
|
-
paths[
|
|
3125
|
-
paths["
|
|
3249
|
+
paths[publicPaths.mediaCollection()] = { get: { summary: "List media items", tags: ["Media"] } };
|
|
3250
|
+
paths[publicPaths.mediaItem().replace(":id", "{id}")] = { get: { summary: "Get media item by id", tags: ["Media"] } };
|
|
3126
3251
|
return c.json({
|
|
3127
3252
|
openapi: "3.0.3",
|
|
3128
3253
|
info: { title: "Manguito CMS Public API", version: "1.0.0" },
|
|
@@ -3137,14 +3262,14 @@ function createCmsApp(options) {
|
|
|
3137
3262
|
paths: {}
|
|
3138
3263
|
})
|
|
3139
3264
|
);
|
|
3140
|
-
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit, programmaticResolver);
|
|
3141
|
-
registerPublicMediaRoutes(app, mediaRepo, listRateLimit);
|
|
3265
|
+
registerPublicContentRoutes(app, registry, publicRepos, projectors, publicPaths, listRateLimit, programmaticResolver);
|
|
3266
|
+
registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
|
|
3142
3267
|
if (options.graphql?.enabled) {
|
|
3143
3268
|
const gqlOptions = options.graphql;
|
|
3144
3269
|
let gqlHandler = null;
|
|
3145
3270
|
let initError = null;
|
|
3146
|
-
const ready = import("./handler-
|
|
3147
|
-
gqlHandler = createGraphQLHandler(registry, graphqlRepos, programmaticResolver, db, gqlOptions);
|
|
3271
|
+
const ready = import("./handler-7LVYEU5C.mjs").then(({ createGraphQLHandler }) => {
|
|
3272
|
+
gqlHandler = createGraphQLHandler(registry, graphqlRepos, fieldKeyMaps, programmaticResolver, db, gqlOptions);
|
|
3148
3273
|
}).catch((err) => {
|
|
3149
3274
|
initError = err;
|
|
3150
3275
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3184,7 +3309,7 @@ function createCmsApp(options) {
|
|
|
3184
3309
|
);
|
|
3185
3310
|
registerSchemaRoute(app, registry, db);
|
|
3186
3311
|
registerUserRoutes(app, db, requirePermission, requireHierarchy);
|
|
3187
|
-
registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePermission, db);
|
|
3312
|
+
registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo, requirePermission, db);
|
|
3188
3313
|
registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, maxFileSize);
|
|
3189
3314
|
return { prefix, app };
|
|
3190
3315
|
}
|
|
@@ -3225,7 +3350,7 @@ function resolveGraphQL(cfg) {
|
|
|
3225
3350
|
};
|
|
3226
3351
|
}
|
|
3227
3352
|
function createAPIAdapter(options = {}) {
|
|
3228
|
-
const prefix = options.prefix
|
|
3353
|
+
const prefix = normalizePrefix(options.prefix);
|
|
3229
3354
|
const media = options.media?.max_file_size !== void 0 ? { max_file_size: options.media.max_file_size } : void 0;
|
|
3230
3355
|
return {
|
|
3231
3356
|
prefix,
|