@bobbykim/manguito-cms-api 0.3.0 → 0.3.3
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-QXMW74MR.mjs → chunk-GM55HGWA.mjs} +24 -6
- package/dist/chunk-GM55HGWA.mjs.map +1 -0
- package/dist/{chunk-FBS2RQI3.mjs → chunk-TK4DNRFF.mjs} +60 -6
- package/dist/chunk-TK4DNRFF.mjs.map +1 -0
- package/dist/graphql/index.js +22 -4
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/index.mjs +2 -2
- package/dist/handler-CGHZG3J5.mjs +8 -0
- package/dist/index.js +262 -142
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -136
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FBS2RQI3.mjs.map +0 -1
- package/dist/chunk-QXMW74MR.mjs.map +0 -1
- package/dist/handler-GMCFYZY5.mjs +0 -8
- /package/dist/{handler-GMCFYZY5.mjs.map → handler-CGHZG3J5.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -57,16 +57,46 @@ async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
|
57
57
|
}
|
|
58
58
|
return ids;
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
function nestedParagraphFields(pType, registry) {
|
|
61
|
+
if (!registry) return [];
|
|
62
|
+
const out = [];
|
|
63
|
+
for (const f of pType.fields) {
|
|
64
|
+
if (f.db_column !== null) continue;
|
|
65
|
+
const comp = f.ui_component;
|
|
66
|
+
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
67
|
+
const nType = registry.paragraph_types[comp.ref];
|
|
68
|
+
if (nType) out.push({ fieldName: f.name, nType });
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
async function fetchParagraphRowIds(db, pType, itemId, fieldName) {
|
|
73
|
+
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
74
|
+
const result = await db.execute(
|
|
75
|
+
import_drizzle_orm2.sql`SELECT id FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
76
|
+
);
|
|
77
|
+
return result.rows.map((r) => r["id"]).filter((id) => typeof id === "string");
|
|
78
|
+
}
|
|
79
|
+
async function persistParagraphField(db, itemId, parentType, fieldName, pType, items, registry) {
|
|
61
80
|
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
81
|
+
const nested = nestedParagraphFields(pType, registry);
|
|
82
|
+
if (nested.length > 0) {
|
|
83
|
+
const staleIds = await fetchParagraphRowIds(db, pType, itemId, fieldName);
|
|
84
|
+
for (const rowId of staleIds) {
|
|
85
|
+
for (const n of nested) {
|
|
86
|
+
const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry);
|
|
87
|
+
removed.push(...d.removed);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
62
91
|
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
63
92
|
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
64
93
|
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
65
94
|
const added = [];
|
|
66
95
|
for (let i = 0; i < items.length; i++) {
|
|
67
96
|
const pItem = items[i];
|
|
97
|
+
const rowId = import_node_crypto.default.randomUUID();
|
|
68
98
|
const data = {
|
|
69
|
-
id:
|
|
99
|
+
id: rowId,
|
|
70
100
|
parent_id: itemId,
|
|
71
101
|
parent_type: parentType,
|
|
72
102
|
parent_field: fieldName,
|
|
@@ -85,11 +115,35 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
|
|
|
85
115
|
const val = pItem[name];
|
|
86
116
|
if (typeof val === "string" && val !== "") added.push(val);
|
|
87
117
|
}
|
|
118
|
+
for (const n of nested) {
|
|
119
|
+
const nestedItems = Array.isArray(pItem[n.fieldName]) ? pItem[n.fieldName] : [];
|
|
120
|
+
const d = await persistParagraphField(
|
|
121
|
+
db,
|
|
122
|
+
rowId,
|
|
123
|
+
pType.db.table_name,
|
|
124
|
+
n.fieldName,
|
|
125
|
+
n.nType,
|
|
126
|
+
nestedItems,
|
|
127
|
+
registry
|
|
128
|
+
);
|
|
129
|
+
added.push(...d.added);
|
|
130
|
+
removed.push(...d.removed);
|
|
131
|
+
}
|
|
88
132
|
}
|
|
89
133
|
return { added, removed };
|
|
90
134
|
}
|
|
91
|
-
async function deleteParagraphField(db, itemId, fieldName, pType) {
|
|
135
|
+
async function deleteParagraphField(db, itemId, fieldName, pType, registry) {
|
|
92
136
|
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
137
|
+
const nested = nestedParagraphFields(pType, registry);
|
|
138
|
+
if (nested.length > 0) {
|
|
139
|
+
const rowIds = await fetchParagraphRowIds(db, pType, itemId, fieldName);
|
|
140
|
+
for (const rowId of rowIds) {
|
|
141
|
+
for (const n of nested) {
|
|
142
|
+
const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry);
|
|
143
|
+
removed.push(...d.removed);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
93
147
|
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
94
148
|
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
95
149
|
return { added: [], removed };
|
|
@@ -152,7 +206,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
152
206
|
const parentIds = rows.map((r) => r["id"]);
|
|
153
207
|
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
154
208
|
const result = await db.execute(
|
|
155
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
209
|
+
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
156
210
|
);
|
|
157
211
|
const byParent = groupBy(result.rows, "parent_id");
|
|
158
212
|
for (const row of rows) {
|
|
@@ -232,7 +286,7 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
|
232
286
|
const parentIds = rows.map((r) => r["id"]);
|
|
233
287
|
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
234
288
|
const result = await db.execute(
|
|
235
|
-
import_drizzle_orm2.sql`SELECT id, parent_id FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
289
|
+
import_drizzle_orm2.sql`SELECT id, parent_id FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
236
290
|
);
|
|
237
291
|
const byParent = groupBy(result.rows, "parent_id");
|
|
238
292
|
for (const row of rows) {
|
|
@@ -493,16 +547,22 @@ function scalarFieldResolver(schemaName) {
|
|
|
493
547
|
function relationFieldResolver(typeName, schemaFieldName) {
|
|
494
548
|
return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
|
|
495
549
|
}
|
|
496
|
-
function programmaticFieldResolver(typeName, schemaFieldName) {
|
|
550
|
+
function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = []) {
|
|
497
551
|
return async (parent, _args, ctx) => {
|
|
498
552
|
let p = ctx.programmaticMemo.get(parent);
|
|
499
553
|
if (!p) {
|
|
500
|
-
p =
|
|
554
|
+
p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames);
|
|
501
555
|
ctx.programmaticMemo.set(parent, p);
|
|
502
556
|
}
|
|
503
557
|
return (await p)[schemaFieldName];
|
|
504
558
|
};
|
|
505
559
|
}
|
|
560
|
+
async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames) {
|
|
561
|
+
if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, parent);
|
|
562
|
+
const enriched = { ...parent };
|
|
563
|
+
await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)));
|
|
564
|
+
return ctx.resolver.resolveItem(typeName, enriched);
|
|
565
|
+
}
|
|
506
566
|
function collectionResolver(typeName, nameMap) {
|
|
507
567
|
return async (_root, args, ctx) => {
|
|
508
568
|
const page = args.page ?? 1;
|
|
@@ -613,6 +673,7 @@ function buildGraphQLSchema(registry) {
|
|
|
613
673
|
return import_graphql5.GraphQLString;
|
|
614
674
|
}
|
|
615
675
|
function buildObjectType(machineName, type) {
|
|
676
|
+
const mediaFieldNames = type.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name);
|
|
616
677
|
return new import_graphql5.GraphQLObjectType({
|
|
617
678
|
name: graphqlTypeName(machineName),
|
|
618
679
|
fields: () => {
|
|
@@ -631,7 +692,7 @@ function buildGraphQLSchema(registry) {
|
|
|
631
692
|
const outType = outputTypeForField(field);
|
|
632
693
|
let resolve;
|
|
633
694
|
if (field.field_type === "programmatic") {
|
|
634
|
-
resolve = programmaticFieldResolver(machineName, field.name);
|
|
695
|
+
resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames);
|
|
635
696
|
} else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
636
697
|
resolve = relationFieldResolver(machineName, field.name);
|
|
637
698
|
} else {
|
|
@@ -845,6 +906,14 @@ var handler_exports = {};
|
|
|
845
906
|
__export(handler_exports, {
|
|
846
907
|
createGraphQLHandler: () => createGraphQLHandler
|
|
847
908
|
});
|
|
909
|
+
function isClientSafeGraphQLError(error) {
|
|
910
|
+
let current = error;
|
|
911
|
+
while (current != null && typeof current === "object" && current.name === "GraphQLError") {
|
|
912
|
+
if (current.originalError == null) return true;
|
|
913
|
+
current = current.originalError;
|
|
914
|
+
}
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
848
917
|
function createGraphQLHandler(registry, repos, resolver, db, options) {
|
|
849
918
|
const schema = buildGraphQLSchema(registry);
|
|
850
919
|
const { plugins } = buildArmorPlugin({
|
|
@@ -856,6 +925,9 @@ function createGraphQLHandler(registry, repos, resolver, db, options) {
|
|
|
856
925
|
graphqlEndpoint: "/graphql",
|
|
857
926
|
graphiql: options.graphiql,
|
|
858
927
|
landingPage: false,
|
|
928
|
+
maskedErrors: {
|
|
929
|
+
maskError: (error, message, isDev) => isClientSafeGraphQLError(error) ? error : (0, import_graphql_yoga.maskError)(error, message, isDev)
|
|
930
|
+
},
|
|
859
931
|
// buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's
|
|
860
932
|
// plugin shapes aren't part of its public typings; Yoga's `plugins` option only
|
|
861
933
|
// accepts its own `Plugin` type, so the cast bridges the two.
|
|
@@ -1841,6 +1913,23 @@ function quoteIdent2(name) {
|
|
|
1841
1913
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
1842
1914
|
return `"${name}"`;
|
|
1843
1915
|
}
|
|
1916
|
+
async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
1917
|
+
const result = await db.execute(
|
|
1918
|
+
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
1919
|
+
);
|
|
1920
|
+
const rows = result.rows;
|
|
1921
|
+
for (const nf of pType.fields) {
|
|
1922
|
+
if (nf.db_column !== null) continue;
|
|
1923
|
+
const comp = nf.ui_component;
|
|
1924
|
+
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
1925
|
+
const nType = registry.paragraph_types[comp.ref];
|
|
1926
|
+
if (!nType) continue;
|
|
1927
|
+
for (const row of rows) {
|
|
1928
|
+
row[nf.name] = await loadParagraphRows(db, registry, nType, row["id"], nf.name);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
return rows;
|
|
1932
|
+
}
|
|
1844
1933
|
async function lookupBasePathId(db, pathOrName) {
|
|
1845
1934
|
const r = await db.execute(
|
|
1846
1935
|
import_drizzle_orm3.sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
|
|
@@ -1992,10 +2081,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
1992
2081
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
1993
2082
|
const pType = registry.paragraph_types[comp.ref];
|
|
1994
2083
|
if (!pType) continue;
|
|
1995
|
-
|
|
1996
|
-
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(pType.db.table_name))} WHERE parent_id = ${id} AND parent_field = ${f.name} ORDER BY "order" ASC`
|
|
1997
|
-
);
|
|
1998
|
-
row[f.name] = r.rows;
|
|
2084
|
+
row[f.name] = await loadParagraphRows(db, registry, pType, id, f.name);
|
|
1999
2085
|
} else if (f.db_column.junction) {
|
|
2000
2086
|
const j = f.db_column.junction;
|
|
2001
2087
|
const r = await db.execute(
|
|
@@ -2070,82 +2156,85 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
2070
2156
|
);
|
|
2071
2157
|
}
|
|
2072
2158
|
}
|
|
2073
|
-
|
|
2074
|
-
|
|
2159
|
+
return writeNewItem(c, body);
|
|
2160
|
+
}
|
|
2161
|
+
);
|
|
2162
|
+
const writeNewItem = async (c, body) => {
|
|
2163
|
+
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
2164
|
+
if (fieldErrors.length > 0) {
|
|
2165
|
+
return c.json(
|
|
2166
|
+
{
|
|
2167
|
+
ok: false,
|
|
2168
|
+
error: {
|
|
2169
|
+
code: "VALIDATION_ERROR",
|
|
2170
|
+
message: "Required fields are missing",
|
|
2171
|
+
details: fieldErrors
|
|
2172
|
+
}
|
|
2173
|
+
},
|
|
2174
|
+
422
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
if (body["published"] === true) {
|
|
2178
|
+
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
2179
|
+
});
|
|
2180
|
+
if (publishDeny) return publishDeny;
|
|
2181
|
+
}
|
|
2182
|
+
const paragraphFields = contentType.fields.filter((f) => f.db_column === null);
|
|
2183
|
+
const junctionFields = contentType.fields.filter(
|
|
2184
|
+
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
2185
|
+
);
|
|
2186
|
+
const columnFields = contentType.fields.filter(
|
|
2187
|
+
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
2188
|
+
);
|
|
2189
|
+
const columnData = {
|
|
2190
|
+
published: body["published"] ?? false
|
|
2191
|
+
};
|
|
2192
|
+
if (contentType.only_one) {
|
|
2193
|
+
columnData["slug"] = typeName;
|
|
2194
|
+
} else {
|
|
2195
|
+
columnData["slug"] = body["slug"];
|
|
2196
|
+
}
|
|
2197
|
+
for (const f of columnFields) {
|
|
2198
|
+
const val = body[f.name];
|
|
2199
|
+
columnData[f.db_column.column_name] = val !== void 0 ? val : null;
|
|
2200
|
+
}
|
|
2201
|
+
if (db) {
|
|
2202
|
+
const basePathId = await lookupBasePathId(db, contentType.default_base_path);
|
|
2203
|
+
if (!basePathId) {
|
|
2075
2204
|
return c.json(
|
|
2076
2205
|
{
|
|
2077
2206
|
ok: false,
|
|
2078
2207
|
error: {
|
|
2079
|
-
code: "
|
|
2080
|
-
message:
|
|
2081
|
-
details: fieldErrors
|
|
2208
|
+
code: "BASE_PATH_NOT_FOUND",
|
|
2209
|
+
message: `Base path '${contentType.default_base_path}' not found \u2014 run manguito migrate`
|
|
2082
2210
|
}
|
|
2083
2211
|
},
|
|
2084
|
-
|
|
2212
|
+
500
|
|
2085
2213
|
);
|
|
2086
2214
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
(
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
published: body["published"] ?? false
|
|
2101
|
-
};
|
|
2102
|
-
if (contentType.only_one) {
|
|
2103
|
-
columnData["slug"] = typeName;
|
|
2104
|
-
} else {
|
|
2105
|
-
columnData["slug"] = body["slug"];
|
|
2106
|
-
}
|
|
2107
|
-
for (const f of columnFields) {
|
|
2108
|
-
const val = body[f.name];
|
|
2109
|
-
columnData[f.db_column.column_name] = val !== void 0 ? val : null;
|
|
2215
|
+
columnData["base_path_id"] = basePathId;
|
|
2216
|
+
}
|
|
2217
|
+
const item = await repo.create(columnData);
|
|
2218
|
+
const itemId = item["id"];
|
|
2219
|
+
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, body)];
|
|
2220
|
+
if (db) {
|
|
2221
|
+
for (const f of paragraphFields) {
|
|
2222
|
+
const comp = f.ui_component;
|
|
2223
|
+
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2224
|
+
const pType = registry.paragraph_types[comp.ref];
|
|
2225
|
+
if (!pType) continue;
|
|
2226
|
+
const items = Array.isArray(body[f.name]) ? body[f.name] : [];
|
|
2227
|
+
mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry));
|
|
2110
2228
|
}
|
|
2111
|
-
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
{
|
|
2116
|
-
ok: false,
|
|
2117
|
-
error: {
|
|
2118
|
-
code: "BASE_PATH_NOT_FOUND",
|
|
2119
|
-
message: `Base path '${contentType.default_base_path}' not found \u2014 run manguito migrate`
|
|
2120
|
-
}
|
|
2121
|
-
},
|
|
2122
|
-
500
|
|
2123
|
-
);
|
|
2124
|
-
}
|
|
2125
|
-
columnData["base_path_id"] = basePathId;
|
|
2229
|
+
for (const f of junctionFields) {
|
|
2230
|
+
const junction = f.db_column.junction;
|
|
2231
|
+
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
|
|
2232
|
+
await persistJunctionField(db, itemId, junction, relatedIds);
|
|
2126
2233
|
}
|
|
2127
|
-
const item = await repo.create(columnData);
|
|
2128
|
-
const itemId = item["id"];
|
|
2129
|
-
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, body)];
|
|
2130
|
-
if (db) {
|
|
2131
|
-
for (const f of paragraphFields) {
|
|
2132
|
-
const comp = f.ui_component;
|
|
2133
|
-
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2134
|
-
const pType = registry.paragraph_types[comp.ref];
|
|
2135
|
-
if (!pType) continue;
|
|
2136
|
-
const items = Array.isArray(body[f.name]) ? body[f.name] : [];
|
|
2137
|
-
mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items));
|
|
2138
|
-
}
|
|
2139
|
-
for (const f of junctionFields) {
|
|
2140
|
-
const junction = f.db_column.junction;
|
|
2141
|
-
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
|
|
2142
|
-
await persistJunctionField(db, itemId, junction, relatedIds);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
2146
|
-
return c.json({ ok: true, data: item }, 201);
|
|
2147
2234
|
}
|
|
2148
|
-
|
|
2235
|
+
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
2236
|
+
return c.json({ ok: true, data: item }, 201);
|
|
2237
|
+
};
|
|
2149
2238
|
app.patch(
|
|
2150
2239
|
`/admin/api/${basePath}/:id`,
|
|
2151
2240
|
requirePermission("content:edit"),
|
|
@@ -2200,71 +2289,92 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
2200
2289
|
);
|
|
2201
2290
|
}
|
|
2202
2291
|
}
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2292
|
+
return writeExistingItem(c, id, existing, body);
|
|
2293
|
+
}
|
|
2294
|
+
);
|
|
2295
|
+
const writeExistingItem = async (c, id, existing, body) => {
|
|
2296
|
+
if (body["published"] === true) {
|
|
2297
|
+
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
2298
|
+
});
|
|
2299
|
+
if (publishDeny) return publishDeny;
|
|
2300
|
+
const merged = { ...existing, ...body };
|
|
2301
|
+
const fieldErrors = checkRequiredFields(requiredFields, merged);
|
|
2302
|
+
if (fieldErrors.length > 0) {
|
|
2303
|
+
return c.json(
|
|
2304
|
+
{
|
|
2305
|
+
ok: false,
|
|
2306
|
+
error: {
|
|
2307
|
+
code: "PUBLISH_VALIDATION_ERROR",
|
|
2308
|
+
message: "Cannot publish \u2014 required fields are missing",
|
|
2309
|
+
details: fieldErrors
|
|
2310
|
+
}
|
|
2311
|
+
},
|
|
2312
|
+
422
|
|
2313
|
+
);
|
|
2222
2314
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
)
|
|
2227
|
-
|
|
2228
|
-
|
|
2315
|
+
}
|
|
2316
|
+
const patchParagraphFields = contentType.fields.filter((f) => f.db_column === null);
|
|
2317
|
+
const patchJunctionFields = contentType.fields.filter(
|
|
2318
|
+
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
2319
|
+
);
|
|
2320
|
+
const patchColumnFields = contentType.fields.filter(
|
|
2321
|
+
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
2322
|
+
);
|
|
2323
|
+
const patchData = {};
|
|
2324
|
+
if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
|
|
2325
|
+
if ("published" in body) patchData["published"] = body["published"];
|
|
2326
|
+
for (const f of patchColumnFields) {
|
|
2327
|
+
if (f.name in body) {
|
|
2328
|
+
const val = body[f.name];
|
|
2329
|
+
patchData[f.db_column.column_name] = val !== void 0 ? val : null;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
const updated = await repo.update(id, patchData);
|
|
2333
|
+
if (!updated) {
|
|
2334
|
+
return c.json(
|
|
2335
|
+
{ ok: false, error: { code: "NOT_FOUND", message: "Not found" } },
|
|
2336
|
+
404
|
|
2229
2337
|
);
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2338
|
+
}
|
|
2339
|
+
const mediaDeltas = [
|
|
2340
|
+
topLevelMediaDelta(mediaFields, existing, body)
|
|
2341
|
+
];
|
|
2342
|
+
if (db) {
|
|
2343
|
+
for (const f of patchParagraphFields) {
|
|
2344
|
+
const comp = f.ui_component;
|
|
2345
|
+
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2346
|
+
const pType = registry.paragraph_types[comp.ref];
|
|
2347
|
+
if (!pType) continue;
|
|
2348
|
+
const items = Array.isArray(body[f.name]) ? body[f.name] : [];
|
|
2349
|
+
mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry));
|
|
2238
2350
|
}
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
404
|
|
2244
|
-
);
|
|
2351
|
+
for (const f of patchJunctionFields) {
|
|
2352
|
+
const junction = f.db_column.junction;
|
|
2353
|
+
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
|
|
2354
|
+
await persistJunctionField(db, id, junction, relatedIds);
|
|
2245
2355
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
const
|
|
2260
|
-
|
|
2261
|
-
|
|
2356
|
+
}
|
|
2357
|
+
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
2358
|
+
return c.json({ ok: true, data: updated });
|
|
2359
|
+
};
|
|
2360
|
+
if (contentType.only_one) {
|
|
2361
|
+
app.put(
|
|
2362
|
+
`/admin/api/${basePath}`,
|
|
2363
|
+
requirePermission("content:edit"),
|
|
2364
|
+
async (c) => {
|
|
2365
|
+
const body = await c.req.json();
|
|
2366
|
+
const existingResult = await repo.findMany({ page: 1, per_page: 1 });
|
|
2367
|
+
const existing = existingResult.data[0];
|
|
2368
|
+
if (!existing) {
|
|
2369
|
+
const createDeny = await requirePermission("content:create")(c, async () => {
|
|
2370
|
+
});
|
|
2371
|
+
if (createDeny) return createDeny;
|
|
2372
|
+
return writeNewItem(c, body);
|
|
2262
2373
|
}
|
|
2374
|
+
return writeExistingItem(c, existing["id"], existing, body);
|
|
2263
2375
|
}
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
}
|
|
2267
|
-
);
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2268
2378
|
app.delete(
|
|
2269
2379
|
`/admin/api/${basePath}/:id`,
|
|
2270
2380
|
requirePermission("content:delete"),
|
|
@@ -2286,7 +2396,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
2286
2396
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2287
2397
|
const pType = registry.paragraph_types[comp.ref];
|
|
2288
2398
|
if (!pType) continue;
|
|
2289
|
-
mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType));
|
|
2399
|
+
mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry));
|
|
2290
2400
|
}
|
|
2291
2401
|
}
|
|
2292
2402
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
@@ -2401,7 +2511,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
2401
2511
|
const pType = registry.paragraph_types[comp.ref];
|
|
2402
2512
|
if (!pType) continue;
|
|
2403
2513
|
const items = Array.isArray(body[f.name]) ? body[f.name] : [];
|
|
2404
|
-
mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items));
|
|
2514
|
+
mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2405
2515
|
}
|
|
2406
2516
|
}
|
|
2407
2517
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
@@ -2470,7 +2580,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
2470
2580
|
const pType = registry.paragraph_types[comp.ref];
|
|
2471
2581
|
if (!pType) continue;
|
|
2472
2582
|
const items = Array.isArray(body[f.name]) ? body[f.name] : [];
|
|
2473
|
-
mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items));
|
|
2583
|
+
mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2474
2584
|
}
|
|
2475
2585
|
}
|
|
2476
2586
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
@@ -2498,7 +2608,7 @@ function registerAdminContentRoutes(app, registry, repos, mediaRepo, requirePerm
|
|
|
2498
2608
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2499
2609
|
const pType = registry.paragraph_types[comp.ref];
|
|
2500
2610
|
if (!pType) continue;
|
|
2501
|
-
mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType));
|
|
2611
|
+
mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry));
|
|
2502
2612
|
}
|
|
2503
2613
|
}
|
|
2504
2614
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
@@ -3466,7 +3576,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3466
3576
|
var import_drizzle_orm6 = require("drizzle-orm");
|
|
3467
3577
|
var API_VERSION = (() => {
|
|
3468
3578
|
try {
|
|
3469
|
-
return "0.3.
|
|
3579
|
+
return "0.3.3";
|
|
3470
3580
|
} catch {
|
|
3471
3581
|
return "0.0.0";
|
|
3472
3582
|
}
|
|
@@ -3948,6 +4058,16 @@ function createCmsApp(options) {
|
|
|
3948
4058
|
])
|
|
3949
4059
|
);
|
|
3950
4060
|
const publicRepos = { ...publicContentRepos, ...publicTaxonomyRepos };
|
|
4061
|
+
const graphqlRepos = Object.fromEntries([
|
|
4062
|
+
...Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
4063
|
+
typeName,
|
|
4064
|
+
createDrizzleContentRepository(db, ct.db.table_name)
|
|
4065
|
+
]),
|
|
4066
|
+
...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
4067
|
+
typeName,
|
|
4068
|
+
createDrizzleContentRepository(db, tt.db.table_name)
|
|
4069
|
+
])
|
|
4070
|
+
]);
|
|
3951
4071
|
registerAuthRoutes(app, db);
|
|
3952
4072
|
app.use("/admin/api/*", authMiddleware);
|
|
3953
4073
|
app.use("/admin/api/*", mustChangePasswordCheck);
|
|
@@ -3989,7 +4109,7 @@ function createCmsApp(options) {
|
|
|
3989
4109
|
let gqlHandler = null;
|
|
3990
4110
|
let initError = null;
|
|
3991
4111
|
const ready = Promise.resolve().then(() => (init_handler(), handler_exports)).then(({ createGraphQLHandler: createGraphQLHandler2 }) => {
|
|
3992
|
-
gqlHandler = createGraphQLHandler2(registry,
|
|
4112
|
+
gqlHandler = createGraphQLHandler2(registry, graphqlRepos, programmaticResolver, db, gqlOptions);
|
|
3993
4113
|
}).catch((err) => {
|
|
3994
4114
|
initError = err;
|
|
3995
4115
|
const message = err instanceof Error ? err.message : String(err);
|