@kyro-cms/core 0.9.7 → 0.9.9
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/{WebhookService-BKszZlG0.d.cts → WebhookService-Dj8pJ3sp.d.cts} +1 -1
- package/dist/{WebhookService-Ccf1j-IN.d.ts → WebhookService-DpRB940h.d.ts} +1 -1
- package/dist/api-handler-graphql.cjs +18 -13
- package/dist/api-handler-graphql.cjs.map +1 -1
- package/dist/api-handler-graphql.js +14 -9
- package/dist/api-handler-graphql.js.map +1 -1
- package/dist/api-handler-trpc.cjs +9 -9
- package/dist/api-handler-trpc.js +7 -7
- package/dist/api-handler.cjs +10 -10
- package/dist/api-handler.js +7 -7
- package/dist/{base-CIuXkrH4.d.cts → base-BjyoF6da.d.cts} +2 -2
- package/dist/{base-fFo4lqER.d.ts → base-C2YR8lWt.d.ts} +2 -2
- package/dist/{chunk-NZEUU7QB.js → chunk-3AKCVYDB.js} +4 -109
- package/dist/chunk-3AKCVYDB.js.map +1 -0
- package/dist/{chunk-FKKQUMXR.cjs → chunk-4OCMVGEY.cjs} +4 -110
- package/dist/chunk-4OCMVGEY.cjs.map +1 -0
- package/dist/{chunk-VLK5SJRI.js → chunk-5RFGYEQ3.js} +133 -46
- package/dist/chunk-5RFGYEQ3.js.map +1 -0
- package/dist/chunk-5RPRLTTN.js +861 -0
- package/dist/chunk-5RPRLTTN.js.map +1 -0
- package/dist/{chunk-AX2TZRQJ.js → chunk-BVUDFKKS.js} +16 -3
- package/dist/chunk-BVUDFKKS.js.map +1 -0
- package/dist/{chunk-DRVOUQMT.cjs → chunk-E4JZ73FY.cjs} +54 -79
- package/dist/chunk-E4JZ73FY.cjs.map +1 -0
- package/dist/{chunk-E5UJBLQ7.js → chunk-FIAMXNTH.js} +2 -2
- package/dist/chunk-FIAMXNTH.js.map +1 -0
- package/dist/{chunk-QVJNSAQL.cjs → chunk-IEIHABVR.cjs} +152 -65
- package/dist/chunk-IEIHABVR.cjs.map +1 -0
- package/dist/{chunk-QFLB4EIJ.cjs → chunk-LNTFRAHF.cjs} +118 -9
- package/dist/chunk-LNTFRAHF.cjs.map +1 -0
- package/dist/chunk-N4GN6Q3V.cjs +869 -0
- package/dist/chunk-N4GN6Q3V.cjs.map +1 -0
- package/dist/{chunk-FSKONGCX.cjs → chunk-OJ2AM75W.cjs} +2 -2
- package/dist/chunk-OJ2AM75W.cjs.map +1 -0
- package/dist/{chunk-QX3WNQ7V.js → chunk-OX6GOIPH.js} +4 -4
- package/dist/{chunk-QX3WNQ7V.js.map → chunk-OX6GOIPH.js.map} +1 -1
- package/dist/{chunk-OZ3CCTTA.cjs → chunk-OXYSOU6Z.cjs} +18 -5
- package/dist/chunk-OXYSOU6Z.cjs.map +1 -0
- package/dist/{chunk-CMXVTUYV.js → chunk-OYCICW4B.js} +38 -63
- package/dist/chunk-OYCICW4B.js.map +1 -0
- package/dist/{chunk-HVCUIII2.cjs → chunk-TQG7G7YI.cjs} +13 -13
- package/dist/{chunk-HVCUIII2.cjs.map → chunk-TQG7G7YI.cjs.map} +1 -1
- package/dist/{chunk-3UK5XBVJ.js → chunk-WDTTHKDS.js} +117 -10
- package/dist/chunk-WDTTHKDS.js.map +1 -0
- package/dist/cli/index.cjs +148 -14
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +148 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/client.cjs +3 -3
- package/dist/client.d.cts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/drizzle/index.d.cts +3 -3
- package/dist/drizzle/index.d.ts +3 -3
- package/dist/fields/index.cjs +21 -21
- package/dist/fields/index.d.cts +1 -1
- package/dist/fields/index.d.ts +1 -1
- package/dist/fields/index.js +1 -1
- package/dist/graphql/index.cjs +13 -5
- package/dist/graphql/index.d.cts +37 -7
- package/dist/graphql/index.d.ts +37 -7
- package/dist/graphql/index.js +2 -2
- package/dist/{index-BKta3cBH.d.cts → index-DHbywDs-.d.cts} +2 -2
- package/dist/{index-ClOqnkTO.d.ts → index-jLs0zlft.d.ts} +2 -2
- package/dist/index.cjs +90 -90
- package/dist/index.d.cts +15 -14
- package/dist/index.d.ts +15 -14
- package/dist/index.js +8 -8
- package/dist/mongodb/index.d.cts +3 -3
- package/dist/mongodb/index.d.ts +3 -3
- package/dist/rest/index.cjs +5 -5
- package/dist/rest/index.d.cts +4 -4
- package/dist/rest/index.d.ts +4 -4
- package/dist/rest/index.js +3 -3
- package/dist/templates/index.d.cts +2 -2
- package/dist/templates/index.d.ts +2 -2
- package/dist/trpc/index.cjs +12 -12
- package/dist/trpc/index.d.cts +3 -3
- package/dist/trpc/index.d.ts +3 -3
- package/dist/trpc/index.js +3 -3
- package/dist/{types-Dgzlftb7.d.ts → types-BzH3_rtP.d.ts} +7 -7
- package/dist/{types-Ds0tCA3L.d.cts → types-CY_iNunL.d.cts} +7 -7
- package/dist/{types-DeSApf9T.d.ts → types-C_rVxU2T.d.cts} +4 -4
- package/dist/{types-DeSApf9T.d.cts → types-C_rVxU2T.d.ts} +4 -4
- package/package.json +2 -1
- package/dist/chunk-3UK5XBVJ.js.map +0 -1
- package/dist/chunk-AX2TZRQJ.js.map +0 -1
- package/dist/chunk-CMXVTUYV.js.map +0 -1
- package/dist/chunk-DRVOUQMT.cjs.map +0 -1
- package/dist/chunk-E5UJBLQ7.js.map +0 -1
- package/dist/chunk-FKKQUMXR.cjs.map +0 -1
- package/dist/chunk-FSKONGCX.cjs.map +0 -1
- package/dist/chunk-NZEUU7QB.js.map +0 -1
- package/dist/chunk-OZ3CCTTA.cjs.map +0 -1
- package/dist/chunk-PONTBXR5.js +0 -842
- package/dist/chunk-PONTBXR5.js.map +0 -1
- package/dist/chunk-QFLB4EIJ.cjs.map +0 -1
- package/dist/chunk-QVJNSAQL.cjs.map +0 -1
- package/dist/chunk-RRKCIAPU.cjs +0 -848
- package/dist/chunk-RRKCIAPU.cjs.map +0 -1
- package/dist/chunk-VLK5SJRI.js.map +0 -1
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk4OCMVGEY_cjs = require('./chunk-4OCMVGEY.cjs');
|
|
4
|
+
var chunkNKPKR5BW_cjs = require('./chunk-NKPKR5BW.cjs');
|
|
5
|
+
var graphql = require('graphql');
|
|
6
|
+
|
|
7
|
+
var RelationLoader = class {
|
|
8
|
+
db;
|
|
9
|
+
tenantID;
|
|
10
|
+
user;
|
|
11
|
+
caches = /* @__PURE__ */ new Map();
|
|
12
|
+
pending = /* @__PURE__ */ new Map();
|
|
13
|
+
waiting = /* @__PURE__ */ new Map();
|
|
14
|
+
flushScheduled = false;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.db = opts.db;
|
|
17
|
+
this.tenantID = opts.tenantID;
|
|
18
|
+
this.user = opts.user;
|
|
19
|
+
}
|
|
20
|
+
getCacheKey(collection) {
|
|
21
|
+
return collection;
|
|
22
|
+
}
|
|
23
|
+
async load(collection, id) {
|
|
24
|
+
const key = this.getCacheKey(collection);
|
|
25
|
+
let cache = this.caches.get(key);
|
|
26
|
+
if (!cache) {
|
|
27
|
+
cache = /* @__PURE__ */ new Map();
|
|
28
|
+
this.caches.set(key, cache);
|
|
29
|
+
}
|
|
30
|
+
if (cache.has(id)) {
|
|
31
|
+
return cache.get(id);
|
|
32
|
+
}
|
|
33
|
+
let pendingSet = this.pending.get(key);
|
|
34
|
+
if (!pendingSet) {
|
|
35
|
+
pendingSet = /* @__PURE__ */ new Set();
|
|
36
|
+
this.pending.set(key, pendingSet);
|
|
37
|
+
}
|
|
38
|
+
pendingSet.add(id);
|
|
39
|
+
cache.set(id, void 0);
|
|
40
|
+
if (!this.flushScheduled) {
|
|
41
|
+
this.flushScheduled = true;
|
|
42
|
+
queueMicrotask(() => {
|
|
43
|
+
this.flushScheduled = false;
|
|
44
|
+
this.flushAll();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
this.waiting.set(`${key}:${id}`, resolve);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async flushAll() {
|
|
52
|
+
for (const [key, ids] of this.pending) {
|
|
53
|
+
if (ids.size === 0) continue;
|
|
54
|
+
const cache = this.caches.get(key);
|
|
55
|
+
const docs = await this.db.find({
|
|
56
|
+
collection: key,
|
|
57
|
+
where: { id: { in: Array.from(ids) } },
|
|
58
|
+
limit: ids.size,
|
|
59
|
+
tenantID: this.tenantID,
|
|
60
|
+
draft: !!this.user
|
|
61
|
+
});
|
|
62
|
+
for (const doc of docs.docs || []) {
|
|
63
|
+
if (doc && doc.id) {
|
|
64
|
+
cache.set(doc.id, doc);
|
|
65
|
+
const resolve = this.waiting.get(`${key}:${doc.id}`);
|
|
66
|
+
if (resolve) {
|
|
67
|
+
resolve(doc);
|
|
68
|
+
this.waiting.delete(`${key}:${doc.id}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const [keyId, resolve] of this.waiting) {
|
|
74
|
+
resolve(void 0);
|
|
75
|
+
}
|
|
76
|
+
this.waiting.clear();
|
|
77
|
+
this.pending.clear();
|
|
78
|
+
}
|
|
79
|
+
resolveOne(collection, id) {
|
|
80
|
+
return this.caches.get(this.getCacheKey(collection))?.get(id);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var GraphQLJSON = new graphql.GraphQLScalarType({
|
|
84
|
+
name: "JSON",
|
|
85
|
+
description: "Arbitrary JSON value",
|
|
86
|
+
serialize(value) {
|
|
87
|
+
return value;
|
|
88
|
+
},
|
|
89
|
+
parseValue(value) {
|
|
90
|
+
return value;
|
|
91
|
+
},
|
|
92
|
+
parseLiteral(ast) {
|
|
93
|
+
switch (ast.kind) {
|
|
94
|
+
case graphql.Kind.STRING:
|
|
95
|
+
return ast.value;
|
|
96
|
+
case graphql.Kind.INT:
|
|
97
|
+
case graphql.Kind.FLOAT:
|
|
98
|
+
return parseFloat(ast.value);
|
|
99
|
+
case graphql.Kind.BOOLEAN:
|
|
100
|
+
return ast.value;
|
|
101
|
+
case graphql.Kind.OBJECT: {
|
|
102
|
+
const obj = {};
|
|
103
|
+
for (const field of ast.fields) {
|
|
104
|
+
obj[field.name.value] = GraphQLJSON.parseLiteral(field.value);
|
|
105
|
+
}
|
|
106
|
+
return obj;
|
|
107
|
+
}
|
|
108
|
+
case graphql.Kind.LIST:
|
|
109
|
+
return ast.values.map((v) => GraphQLJSON.parseLiteral(v));
|
|
110
|
+
case graphql.Kind.NULL:
|
|
111
|
+
return null;
|
|
112
|
+
default:
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
function depthLimit(maxDepth) {
|
|
118
|
+
return (validationContext) => {
|
|
119
|
+
let depth = 0;
|
|
120
|
+
return {
|
|
121
|
+
Field() {
|
|
122
|
+
depth++;
|
|
123
|
+
if (depth > maxDepth) {
|
|
124
|
+
validationContext.reportError(
|
|
125
|
+
new Error(`Query exceeds max depth of ${maxDepth}`)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
leaveField() {
|
|
130
|
+
depth--;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function toPascal(slug) {
|
|
136
|
+
return slug.split(/[-_]/).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
137
|
+
}
|
|
138
|
+
function toCamel(slug) {
|
|
139
|
+
const p = toPascal(slug);
|
|
140
|
+
return p.charAt(0).toLowerCase() + p.slice(1);
|
|
141
|
+
}
|
|
142
|
+
function toSingular(slug) {
|
|
143
|
+
return slug.endsWith("s") && slug.length > 1 ? slug.slice(0, -1) : slug;
|
|
144
|
+
}
|
|
145
|
+
async function checkGraphQLAccess(collection, operation, context) {
|
|
146
|
+
const accessRule = collection.access?.[operation];
|
|
147
|
+
if (context.apiKey) {
|
|
148
|
+
if (context.apiKey.permissions?.length > 0) {
|
|
149
|
+
const resource = collection.slug;
|
|
150
|
+
const action = operation === "read" ? "read" : operation === "create" ? "create" : "update";
|
|
151
|
+
const permission = `${resource}:${action}`;
|
|
152
|
+
if (!chunk4OCMVGEY_cjs.hasApiKeyPermission(context.apiKey.permissions, permission) && !chunk4OCMVGEY_cjs.hasApiKeyPermission(context.apiKey.permissions, `${resource}:admin`)) {
|
|
153
|
+
return { allowed: false };
|
|
154
|
+
}
|
|
155
|
+
return { allowed: true };
|
|
156
|
+
}
|
|
157
|
+
if (accessRule) {
|
|
158
|
+
const result2 = await chunk4OCMVGEY_cjs.evaluateAccess(accessRule, {
|
|
159
|
+
req: context.req,
|
|
160
|
+
user: context.user,
|
|
161
|
+
tenantID: context.tenantID
|
|
162
|
+
});
|
|
163
|
+
if (typeof result2 === "boolean") {
|
|
164
|
+
return { allowed: result2 };
|
|
165
|
+
}
|
|
166
|
+
return { allowed: true, extraWhere: result2 };
|
|
167
|
+
}
|
|
168
|
+
return { allowed: true };
|
|
169
|
+
}
|
|
170
|
+
if (context.user) {
|
|
171
|
+
const resource = collection.slug;
|
|
172
|
+
const action = operation === "read" ? "read" : operation === "create" ? "create" : operation === "update" ? "update" : "delete";
|
|
173
|
+
const permission = `${resource}:${action}`;
|
|
174
|
+
const userHasPermission = chunkNKPKR5BW_cjs.hasPermission(
|
|
175
|
+
{ id: context.user.id, email: context.user.email, role: context.user.role },
|
|
176
|
+
permission
|
|
177
|
+
);
|
|
178
|
+
if (!userHasPermission && !chunkNKPKR5BW_cjs.hasPermission(
|
|
179
|
+
{ id: context.user.id, email: context.user.email, role: context.user.role },
|
|
180
|
+
`${resource}:admin`
|
|
181
|
+
)) {
|
|
182
|
+
if (!accessRule) {
|
|
183
|
+
return { allowed: false };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!accessRule) {
|
|
188
|
+
if (!context.user) {
|
|
189
|
+
return { allowed: false };
|
|
190
|
+
}
|
|
191
|
+
return { allowed: true };
|
|
192
|
+
}
|
|
193
|
+
const result = await chunk4OCMVGEY_cjs.evaluateAccess(accessRule, {
|
|
194
|
+
req: context.req,
|
|
195
|
+
user: context.user,
|
|
196
|
+
tenantID: context.tenantID
|
|
197
|
+
});
|
|
198
|
+
if (typeof result === "boolean") {
|
|
199
|
+
return { allowed: result };
|
|
200
|
+
}
|
|
201
|
+
return { allowed: true, extraWhere: result };
|
|
202
|
+
}
|
|
203
|
+
async function checkGlobalAccess(global, operation, context) {
|
|
204
|
+
const accessRule = global.access?.[operation];
|
|
205
|
+
if (context.apiKey) {
|
|
206
|
+
if (context.apiKey.permissions?.length > 0) {
|
|
207
|
+
const resource = global.slug;
|
|
208
|
+
const permission = `${resource}:${operation}`;
|
|
209
|
+
if (!chunk4OCMVGEY_cjs.hasApiKeyPermission(context.apiKey.permissions, permission) && !chunk4OCMVGEY_cjs.hasApiKeyPermission(context.apiKey.permissions, `${resource}:admin`) && !chunk4OCMVGEY_cjs.hasApiKeyPermission(context.apiKey.permissions, `${resource}:*`)) {
|
|
210
|
+
return { allowed: false };
|
|
211
|
+
}
|
|
212
|
+
return { allowed: true };
|
|
213
|
+
}
|
|
214
|
+
if (accessRule) {
|
|
215
|
+
const result2 = await chunk4OCMVGEY_cjs.evaluateAccess(accessRule, {
|
|
216
|
+
req: context.req,
|
|
217
|
+
user: context.user,
|
|
218
|
+
tenantID: context.tenantID
|
|
219
|
+
});
|
|
220
|
+
return { allowed: typeof result2 === "boolean" ? result2 : true };
|
|
221
|
+
}
|
|
222
|
+
return { allowed: true };
|
|
223
|
+
}
|
|
224
|
+
if (!accessRule) {
|
|
225
|
+
if (!context.user) {
|
|
226
|
+
return { allowed: false };
|
|
227
|
+
}
|
|
228
|
+
const userRole = context.user.role;
|
|
229
|
+
if (userRole === "super_admin" || userRole === "admin") {
|
|
230
|
+
return { allowed: true };
|
|
231
|
+
}
|
|
232
|
+
return { allowed: false };
|
|
233
|
+
}
|
|
234
|
+
const result = await chunk4OCMVGEY_cjs.evaluateAccess(accessRule, {
|
|
235
|
+
req: context.req,
|
|
236
|
+
user: context.user,
|
|
237
|
+
tenantID: context.tenantID
|
|
238
|
+
});
|
|
239
|
+
if (typeof result === "boolean") {
|
|
240
|
+
return { allowed: result };
|
|
241
|
+
}
|
|
242
|
+
return { allowed: true };
|
|
243
|
+
}
|
|
244
|
+
function resolveMediaUrl(url, origin) {
|
|
245
|
+
if (!url) return null;
|
|
246
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("blob:") || url.startsWith("data:")) return url;
|
|
247
|
+
const base = (process.env.KYRO_BASE_URL || origin || "http://localhost:4321").replace(/\/+$/, "");
|
|
248
|
+
return `${base}${url.startsWith("/") ? "" : "/"}${url}`;
|
|
249
|
+
}
|
|
250
|
+
function processMediaDoc(doc, origin) {
|
|
251
|
+
if (!doc) return doc;
|
|
252
|
+
if (doc.url) doc.url = resolveMediaUrl(doc.url, origin);
|
|
253
|
+
if (doc.thumbnailUrl) doc.thumbnailUrl = resolveMediaUrl(doc.thumbnailUrl, origin);
|
|
254
|
+
return doc;
|
|
255
|
+
}
|
|
256
|
+
function fieldToGraphQLType(field, registry, collectionTypes, isInputType = false) {
|
|
257
|
+
switch (field.type) {
|
|
258
|
+
case "text":
|
|
259
|
+
case "email":
|
|
260
|
+
case "password":
|
|
261
|
+
case "textarea":
|
|
262
|
+
case "color":
|
|
263
|
+
case "code":
|
|
264
|
+
case "markdown":
|
|
265
|
+
case "date":
|
|
266
|
+
case "select":
|
|
267
|
+
case "radio":
|
|
268
|
+
return graphql.GraphQLString;
|
|
269
|
+
case "upload": {
|
|
270
|
+
const uploadField = field;
|
|
271
|
+
if (isInputType) {
|
|
272
|
+
return uploadField.hasMany ? new graphql.GraphQLList(graphql.GraphQLString) : graphql.GraphQLString;
|
|
273
|
+
}
|
|
274
|
+
if (uploadField.relationTo && collectionTypes?.[uploadField.relationTo]) {
|
|
275
|
+
const refType = collectionTypes[uploadField.relationTo];
|
|
276
|
+
return uploadField.hasMany ? new graphql.GraphQLList(refType) : refType;
|
|
277
|
+
}
|
|
278
|
+
return GraphQLJSON;
|
|
279
|
+
}
|
|
280
|
+
case "image": {
|
|
281
|
+
if (isInputType) return graphql.GraphQLString;
|
|
282
|
+
const imageField = field;
|
|
283
|
+
if (imageField.relationTo && collectionTypes?.[imageField.relationTo]) {
|
|
284
|
+
return collectionTypes[imageField.relationTo];
|
|
285
|
+
}
|
|
286
|
+
return GraphQLJSON;
|
|
287
|
+
}
|
|
288
|
+
case "number":
|
|
289
|
+
return field.integer ? graphql.GraphQLInt : graphql.GraphQLFloat;
|
|
290
|
+
case "checkbox":
|
|
291
|
+
return graphql.GraphQLBoolean;
|
|
292
|
+
case "json":
|
|
293
|
+
case "richtext":
|
|
294
|
+
case "blocks":
|
|
295
|
+
case "tabs":
|
|
296
|
+
return isInputType ? GraphQLJSON : GraphQLJSON;
|
|
297
|
+
case "relationship":
|
|
298
|
+
if (typeof field.relationTo === "string") {
|
|
299
|
+
if (isInputType) {
|
|
300
|
+
return field.hasMany ? new graphql.GraphQLList(graphql.GraphQLString) : graphql.GraphQLString;
|
|
301
|
+
}
|
|
302
|
+
if (collectionTypes?.[field.relationTo]) {
|
|
303
|
+
return field.hasMany ? new graphql.GraphQLList(collectionTypes[field.relationTo]) : collectionTypes[field.relationTo];
|
|
304
|
+
}
|
|
305
|
+
const relatedCollection = registry.getCollection(field.relationTo);
|
|
306
|
+
if (relatedCollection) {
|
|
307
|
+
return new graphql.GraphQLObjectType({
|
|
308
|
+
name: `${toPascal(field.relationTo)}Ref`,
|
|
309
|
+
fields: () => ({
|
|
310
|
+
id: { type: graphql.GraphQLString },
|
|
311
|
+
...buildFieldsFromCollection(relatedCollection, registry, collectionTypes)
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return graphql.GraphQLString;
|
|
317
|
+
case "array":
|
|
318
|
+
return new graphql.GraphQLList(graphql.GraphQLString);
|
|
319
|
+
case "group":
|
|
320
|
+
return isInputType ? GraphQLJSON : GraphQLJSON;
|
|
321
|
+
default:
|
|
322
|
+
return graphql.GraphQLString;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function buildFieldsFromCollection(config, registry, collectionTypes) {
|
|
326
|
+
const fields = {};
|
|
327
|
+
for (const field of config.fields) {
|
|
328
|
+
if (field.name && field.admin?.hidden !== true) {
|
|
329
|
+
const fieldConfig = buildFieldConfig(field, registry, collectionTypes);
|
|
330
|
+
if (fieldConfig) {
|
|
331
|
+
fields[field.name] = fieldConfig;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return fields;
|
|
336
|
+
}
|
|
337
|
+
function buildFieldConfig(field, registry, collectionTypes) {
|
|
338
|
+
if (!field.name) return null;
|
|
339
|
+
const deprecationReason = field.deprecated || void 0;
|
|
340
|
+
if (field.type === "upload") {
|
|
341
|
+
const gqlType2 = fieldToGraphQLType(field, registry, collectionTypes, false);
|
|
342
|
+
const uploadField = field;
|
|
343
|
+
return {
|
|
344
|
+
type: field.required ? new graphql.GraphQLNonNull(gqlType2) : gqlType2,
|
|
345
|
+
description: field.admin?.description || field.label,
|
|
346
|
+
deprecationReason,
|
|
347
|
+
resolve: async (source, args, ctx) => {
|
|
348
|
+
const loader = ctx?.relationLoader;
|
|
349
|
+
const value = source?.[uploadField.name];
|
|
350
|
+
const origin = ctx?.req?.url ? new URL(ctx.req.url).origin : void 0;
|
|
351
|
+
if (!value) return null;
|
|
352
|
+
if (uploadField.hasMany) {
|
|
353
|
+
const items = Array.isArray(value) ? value : [];
|
|
354
|
+
return Promise.all(items.map(async (item) => {
|
|
355
|
+
const id2 = typeof item === "string" ? item : item?.id;
|
|
356
|
+
if (!id2) return processMediaDoc(item, origin);
|
|
357
|
+
if (typeof item === "object" && item?.url) return processMediaDoc(item, origin);
|
|
358
|
+
if (loader) {
|
|
359
|
+
const cached = loader.resolveOne(uploadField.relationTo, id2);
|
|
360
|
+
if (cached) return processMediaDoc(cached, origin);
|
|
361
|
+
const loaded = await loader.load(uploadField.relationTo, id2);
|
|
362
|
+
if (loaded) return processMediaDoc(loaded, origin);
|
|
363
|
+
}
|
|
364
|
+
return { id: id2 };
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
const id = typeof value === "string" ? value : value?.id;
|
|
368
|
+
if (!id) return processMediaDoc(value, origin);
|
|
369
|
+
if (typeof value === "object" && value?.url) return processMediaDoc(value, origin);
|
|
370
|
+
if (loader) {
|
|
371
|
+
const cached = loader.resolveOne(uploadField.relationTo, id);
|
|
372
|
+
if (cached) return processMediaDoc(cached, origin);
|
|
373
|
+
const loaded = await loader.load(uploadField.relationTo, id);
|
|
374
|
+
if (loaded) return processMediaDoc(loaded, origin);
|
|
375
|
+
}
|
|
376
|
+
return { id };
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (field.type === "relationship" && !field.hasMany) {
|
|
381
|
+
const gqlType2 = fieldToGraphQLType(field, registry, collectionTypes, false);
|
|
382
|
+
return {
|
|
383
|
+
type: field.required ? new graphql.GraphQLNonNull(gqlType2) : gqlType2,
|
|
384
|
+
description: field.admin?.description || field.label,
|
|
385
|
+
deprecationReason,
|
|
386
|
+
resolve: async (source, args, ctx) => {
|
|
387
|
+
const loader = ctx?.relationLoader;
|
|
388
|
+
const relField = field;
|
|
389
|
+
const id = source?.[relField.name];
|
|
390
|
+
if (!id) return null;
|
|
391
|
+
if (loader && typeof id === "string") {
|
|
392
|
+
const cached = loader.resolveOne(relField.relationTo, id);
|
|
393
|
+
if (cached) return cached;
|
|
394
|
+
const loaded = await loader.load(relField.relationTo, id);
|
|
395
|
+
if (loaded) return loaded;
|
|
396
|
+
}
|
|
397
|
+
return { id };
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (field.type === "relationship" && field.hasMany) {
|
|
402
|
+
const gqlType2 = fieldToGraphQLType(field, registry, collectionTypes, false);
|
|
403
|
+
return {
|
|
404
|
+
type: gqlType2,
|
|
405
|
+
description: field.admin?.description || field.label,
|
|
406
|
+
deprecationReason,
|
|
407
|
+
resolve: async (source, args, ctx) => {
|
|
408
|
+
const loader = ctx?.relationLoader;
|
|
409
|
+
const relField = field;
|
|
410
|
+
const ids = source?.[relField.name] || [];
|
|
411
|
+
if (!ids.length) return [];
|
|
412
|
+
if (loader) {
|
|
413
|
+
const results = [];
|
|
414
|
+
for (const id of ids) {
|
|
415
|
+
const cached = loader.resolveOne(relField.relationTo, id);
|
|
416
|
+
if (cached) results.push(cached);
|
|
417
|
+
else {
|
|
418
|
+
const loaded = await loader.load(relField.relationTo, id);
|
|
419
|
+
results.push(loaded || { id });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return results;
|
|
423
|
+
}
|
|
424
|
+
return ids.map((id) => ({ id }));
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const gqlType = fieldToGraphQLType(field, registry, collectionTypes, false);
|
|
429
|
+
return {
|
|
430
|
+
type: field.required ? new graphql.GraphQLNonNull(gqlType) : gqlType,
|
|
431
|
+
description: field.admin?.description || field.label,
|
|
432
|
+
deprecationReason
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function buildGraphQLSchema(options) {
|
|
436
|
+
const { registry, db, user, req, tenantID, apiKey, settings } = options;
|
|
437
|
+
const apiAccess = settings?.access?.apiAccess;
|
|
438
|
+
if (apiAccess?.graphqlEnabled === false) {
|
|
439
|
+
throw new Error("GraphQL API is disabled");
|
|
440
|
+
}
|
|
441
|
+
const collections = registry.getCollections();
|
|
442
|
+
const globals = registry.getGlobals();
|
|
443
|
+
const collectionTypes = {};
|
|
444
|
+
const collectionInputTypes = {};
|
|
445
|
+
for (const collection of collections) {
|
|
446
|
+
collectionTypes[collection.slug] = new graphql.GraphQLObjectType({
|
|
447
|
+
name: toPascal(toSingular(collection.slug)),
|
|
448
|
+
fields: () => ({
|
|
449
|
+
id: { type: graphql.GraphQLString },
|
|
450
|
+
...buildFieldsFromCollection(collection, registry, collectionTypes),
|
|
451
|
+
...collection.timestamps ? {
|
|
452
|
+
createdAt: { type: graphql.GraphQLString },
|
|
453
|
+
updatedAt: { type: graphql.GraphQLString }
|
|
454
|
+
} : {},
|
|
455
|
+
...collection.tenantScoped ? {
|
|
456
|
+
tenantID: { type: graphql.GraphQLString }
|
|
457
|
+
} : {}
|
|
458
|
+
})
|
|
459
|
+
});
|
|
460
|
+
const inputFields = {};
|
|
461
|
+
for (const field of collection.fields) {
|
|
462
|
+
if (field.name && field.name !== "id") {
|
|
463
|
+
inputFields[field.name] = {
|
|
464
|
+
type: fieldToGraphQLType(field, registry, collectionTypes, true)
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
collectionInputTypes[collection.slug] = new graphql.GraphQLInputObjectType({
|
|
469
|
+
name: `${toPascal(toSingular(collection.slug))}Input`,
|
|
470
|
+
fields: () => inputFields
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
const globalTypes = {};
|
|
474
|
+
const globalInputTypes = {};
|
|
475
|
+
for (const global of globals) {
|
|
476
|
+
globalTypes[global.slug] = new graphql.GraphQLObjectType({
|
|
477
|
+
name: `${toPascal(global.slug)}Global`,
|
|
478
|
+
fields: () => ({
|
|
479
|
+
id: { type: graphql.GraphQLString },
|
|
480
|
+
...buildFieldsFromCollection(
|
|
481
|
+
{ slug: global.slug, fields: global.fields },
|
|
482
|
+
registry,
|
|
483
|
+
collectionTypes
|
|
484
|
+
)
|
|
485
|
+
})
|
|
486
|
+
});
|
|
487
|
+
globalInputTypes[global.slug] = new graphql.GraphQLInputObjectType({
|
|
488
|
+
name: `${toPascal(global.slug)}GlobalInput`,
|
|
489
|
+
fields: () => {
|
|
490
|
+
const inputFields = {};
|
|
491
|
+
for (const field of global.fields) {
|
|
492
|
+
if (field.name && field.name !== "id") {
|
|
493
|
+
inputFields[field.name] = {
|
|
494
|
+
type: fieldToGraphQLType(field, registry, collectionTypes, true)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return inputFields;
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
const queryFields = {};
|
|
503
|
+
for (const collection of collections) {
|
|
504
|
+
const type = collectionTypes[collection.slug];
|
|
505
|
+
if (!type) continue;
|
|
506
|
+
queryFields[toCamel(collection.slug)] = {
|
|
507
|
+
type: new graphql.GraphQLObjectType({
|
|
508
|
+
name: `${toPascal(toSingular(collection.slug))}FindResult`,
|
|
509
|
+
fields: {
|
|
510
|
+
docs: { type: new graphql.GraphQLList(type) },
|
|
511
|
+
totalDocs: { type: graphql.GraphQLInt },
|
|
512
|
+
page: { type: graphql.GraphQLInt },
|
|
513
|
+
totalPages: { type: graphql.GraphQLInt },
|
|
514
|
+
hasNextPage: { type: graphql.GraphQLBoolean },
|
|
515
|
+
hasPrevPage: { type: graphql.GraphQLBoolean }
|
|
516
|
+
}
|
|
517
|
+
}),
|
|
518
|
+
args: {
|
|
519
|
+
where: { type: graphql.GraphQLString },
|
|
520
|
+
sort: { type: graphql.GraphQLString },
|
|
521
|
+
limit: { type: graphql.GraphQLInt },
|
|
522
|
+
page: { type: graphql.GraphQLInt },
|
|
523
|
+
draft: { type: graphql.GraphQLBoolean }
|
|
524
|
+
},
|
|
525
|
+
resolve: async (_, args, context) => {
|
|
526
|
+
const access = await checkGraphQLAccess(collection, "read", {
|
|
527
|
+
user: context.user,
|
|
528
|
+
req: context.req,
|
|
529
|
+
tenantID: context.tenantID,
|
|
530
|
+
apiKey: context.apiKey
|
|
531
|
+
});
|
|
532
|
+
if (!access.allowed) {
|
|
533
|
+
throw new Error("Access denied");
|
|
534
|
+
}
|
|
535
|
+
if (context.tenantID) {
|
|
536
|
+
db.setTenantContext({ tenantId: context.tenantID, userId: context.user?.id ?? "", role: context.user?.role, isSuperAdmin: context.user?.role === "super_admin" });
|
|
537
|
+
}
|
|
538
|
+
let where = {};
|
|
539
|
+
if (args.where) {
|
|
540
|
+
try {
|
|
541
|
+
where = JSON.parse(args.where);
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (access.extraWhere) {
|
|
546
|
+
where = { ...where, ...access.extraWhere };
|
|
547
|
+
}
|
|
548
|
+
const isDraft = args.draft ?? !!context.user;
|
|
549
|
+
return db.find({
|
|
550
|
+
collection: collection.slug,
|
|
551
|
+
where,
|
|
552
|
+
sort: args.sort,
|
|
553
|
+
limit: args.limit || 10,
|
|
554
|
+
page: args.page || 1,
|
|
555
|
+
tenantID: context.tenantID,
|
|
556
|
+
draft: isDraft
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
queryFields[toCamel(toSingular(collection.slug))] = {
|
|
561
|
+
type,
|
|
562
|
+
args: {
|
|
563
|
+
id: { type: graphql.GraphQLString },
|
|
564
|
+
slug: { type: graphql.GraphQLString },
|
|
565
|
+
draft: { type: graphql.GraphQLBoolean }
|
|
566
|
+
},
|
|
567
|
+
resolve: async (_, args, context) => {
|
|
568
|
+
if (!args.id && !args.slug) {
|
|
569
|
+
throw new Error("Either id or slug is required");
|
|
570
|
+
}
|
|
571
|
+
const access = await checkGraphQLAccess(collection, "read", {
|
|
572
|
+
user: context.user,
|
|
573
|
+
req: context.req,
|
|
574
|
+
tenantID: context.tenantID,
|
|
575
|
+
apiKey: context.apiKey
|
|
576
|
+
});
|
|
577
|
+
if (!access.allowed) {
|
|
578
|
+
throw new Error("Access denied");
|
|
579
|
+
}
|
|
580
|
+
if (context.tenantID) {
|
|
581
|
+
db.setTenantContext({ tenantId: context.tenantID, userId: context.user?.id ?? "", role: context.user?.role, isSuperAdmin: context.user?.role === "super_admin" });
|
|
582
|
+
}
|
|
583
|
+
const isDraft = args.draft ?? !!context.user;
|
|
584
|
+
if (args.id) {
|
|
585
|
+
return db.findByID({
|
|
586
|
+
collection: collection.slug,
|
|
587
|
+
id: args.id,
|
|
588
|
+
tenantID: context.tenantID,
|
|
589
|
+
draft: isDraft
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
const result = await db.find({
|
|
593
|
+
collection: collection.slug,
|
|
594
|
+
where: { slug: { equals: args.slug } },
|
|
595
|
+
limit: 1,
|
|
596
|
+
page: 1,
|
|
597
|
+
tenantID: context.tenantID,
|
|
598
|
+
draft: isDraft
|
|
599
|
+
});
|
|
600
|
+
return result.docs?.[0] || null;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
queryFields[`count${toPascal(collection.slug)}`] = {
|
|
604
|
+
type: new graphql.GraphQLObjectType({
|
|
605
|
+
name: `${toPascal(toSingular(collection.slug))}Count`,
|
|
606
|
+
fields: {
|
|
607
|
+
totalDocs: { type: graphql.GraphQLInt }
|
|
608
|
+
}
|
|
609
|
+
}),
|
|
610
|
+
args: {
|
|
611
|
+
where: { type: graphql.GraphQLString }
|
|
612
|
+
},
|
|
613
|
+
resolve: async (_, args, context) => {
|
|
614
|
+
const access = await checkGraphQLAccess(collection, "read", {
|
|
615
|
+
user: context.user,
|
|
616
|
+
req: context.req,
|
|
617
|
+
tenantID: context.tenantID,
|
|
618
|
+
apiKey: context.apiKey
|
|
619
|
+
});
|
|
620
|
+
if (!access.allowed) {
|
|
621
|
+
return { totalDocs: 0 };
|
|
622
|
+
}
|
|
623
|
+
let where = {};
|
|
624
|
+
if (args.where) {
|
|
625
|
+
try {
|
|
626
|
+
where = JSON.parse(args.where);
|
|
627
|
+
} catch {
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const count = await db.count({
|
|
631
|
+
collection: collection.slug,
|
|
632
|
+
where,
|
|
633
|
+
tenantID: context.tenantID
|
|
634
|
+
});
|
|
635
|
+
return { totalDocs: count };
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
for (const global of globals) {
|
|
640
|
+
const type = globalTypes[global.slug];
|
|
641
|
+
if (!type) continue;
|
|
642
|
+
queryFields[toCamel(toSingular(global.slug))] = {
|
|
643
|
+
type,
|
|
644
|
+
resolve: async (_, args, context) => {
|
|
645
|
+
const access = await checkGlobalAccess(global, "read", {
|
|
646
|
+
user: context.user,
|
|
647
|
+
req: context.req,
|
|
648
|
+
tenantID: context.tenantID,
|
|
649
|
+
apiKey: context.apiKey
|
|
650
|
+
});
|
|
651
|
+
if (!access.allowed) {
|
|
652
|
+
throw new Error("Access denied: cannot read global");
|
|
653
|
+
}
|
|
654
|
+
if (context.tenantID) {
|
|
655
|
+
db.setTenantContext({ tenantId: context.tenantID, userId: context.user?.id ?? "", role: context.user?.role, isSuperAdmin: context.user?.role === "super_admin" });
|
|
656
|
+
}
|
|
657
|
+
return db.findOne({
|
|
658
|
+
collection: `_globals_${global.slug}`,
|
|
659
|
+
where: {},
|
|
660
|
+
tenantID: context.tenantID
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const Query = new graphql.GraphQLObjectType({
|
|
666
|
+
name: "Query",
|
|
667
|
+
fields: queryFields
|
|
668
|
+
});
|
|
669
|
+
const mutationFields = {};
|
|
670
|
+
for (const collection of collections) {
|
|
671
|
+
const type = collectionTypes[collection.slug];
|
|
672
|
+
const inputType = collectionInputTypes[collection.slug];
|
|
673
|
+
if (!type || !inputType) continue;
|
|
674
|
+
mutationFields[`create${toPascal(toSingular(collection.slug))}`] = {
|
|
675
|
+
type: new graphql.GraphQLObjectType({
|
|
676
|
+
name: `${toPascal(toSingular(collection.slug))}CreateResult`,
|
|
677
|
+
fields: {
|
|
678
|
+
doc: { type },
|
|
679
|
+
message: { type: graphql.GraphQLString }
|
|
680
|
+
}
|
|
681
|
+
}),
|
|
682
|
+
args: {
|
|
683
|
+
data: { type: new graphql.GraphQLNonNull(inputType) }
|
|
684
|
+
},
|
|
685
|
+
resolve: async (_, args, context) => {
|
|
686
|
+
const access = await checkGraphQLAccess(collection, "create", {
|
|
687
|
+
user: context.user,
|
|
688
|
+
req: context.req,
|
|
689
|
+
tenantID: context.tenantID,
|
|
690
|
+
apiKey: context.apiKey
|
|
691
|
+
});
|
|
692
|
+
if (!access.allowed) {
|
|
693
|
+
throw new Error("Access denied: cannot create");
|
|
694
|
+
}
|
|
695
|
+
if (context.tenantID) {
|
|
696
|
+
db.setTenantContext({ tenantId: context.tenantID, userId: context.user?.id ?? "", role: context.user?.role, isSuperAdmin: context.user?.role === "super_admin" });
|
|
697
|
+
}
|
|
698
|
+
const schema = registry.getCreateZodSchema(collection.slug);
|
|
699
|
+
const validated = schema.parse(args.data);
|
|
700
|
+
const doc = await db.create({
|
|
701
|
+
collection: collection.slug,
|
|
702
|
+
data: validated,
|
|
703
|
+
tenantID: context.tenantID
|
|
704
|
+
});
|
|
705
|
+
return { doc, message: "Created successfully" };
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
mutationFields[`update${toPascal(toSingular(collection.slug))}`] = {
|
|
709
|
+
type: new graphql.GraphQLObjectType({
|
|
710
|
+
name: `${toPascal(toSingular(collection.slug))}UpdateResult`,
|
|
711
|
+
fields: {
|
|
712
|
+
doc: { type },
|
|
713
|
+
message: { type: graphql.GraphQLString }
|
|
714
|
+
}
|
|
715
|
+
}),
|
|
716
|
+
args: {
|
|
717
|
+
id: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) },
|
|
718
|
+
data: { type: new graphql.GraphQLNonNull(inputType) },
|
|
719
|
+
baseUpdatedAt: { type: graphql.GraphQLString }
|
|
720
|
+
},
|
|
721
|
+
resolve: async (_, args, context) => {
|
|
722
|
+
const access = await checkGraphQLAccess(collection, "update", {
|
|
723
|
+
user: context.user,
|
|
724
|
+
req: context.req,
|
|
725
|
+
tenantID: context.tenantID,
|
|
726
|
+
apiKey: context.apiKey
|
|
727
|
+
});
|
|
728
|
+
if (!access.allowed) {
|
|
729
|
+
throw new Error("Access denied: cannot update");
|
|
730
|
+
}
|
|
731
|
+
if (context.tenantID) {
|
|
732
|
+
db.setTenantContext({ tenantId: context.tenantID, userId: context.user?.id ?? "", role: context.user?.role, isSuperAdmin: context.user?.role === "super_admin" });
|
|
733
|
+
}
|
|
734
|
+
if (args.baseUpdatedAt) {
|
|
735
|
+
const originalDoc = await db.findByID({
|
|
736
|
+
collection: collection.slug,
|
|
737
|
+
id: args.id,
|
|
738
|
+
tenantID: context.tenantID,
|
|
739
|
+
draft: true
|
|
740
|
+
});
|
|
741
|
+
if (originalDoc && originalDoc.updatedAt && args.baseUpdatedAt !== originalDoc.updatedAt) {
|
|
742
|
+
throw new Error(`Revision conflict: document has changed since ${args.baseUpdatedAt}. Current updatedAt: ${originalDoc.updatedAt}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const schema = registry.getUpdateZodSchema(collection.slug);
|
|
746
|
+
const validated = schema.parse(args.data);
|
|
747
|
+
const doc = await db.update({
|
|
748
|
+
collection: collection.slug,
|
|
749
|
+
id: args.id,
|
|
750
|
+
data: validated,
|
|
751
|
+
tenantID: context.tenantID
|
|
752
|
+
});
|
|
753
|
+
return { doc, message: "Updated successfully" };
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
mutationFields[`delete${toPascal(toSingular(collection.slug))}`] = {
|
|
757
|
+
type: new graphql.GraphQLObjectType({
|
|
758
|
+
name: `${toPascal(toSingular(collection.slug))}DeleteResult`,
|
|
759
|
+
fields: {
|
|
760
|
+
doc: { type },
|
|
761
|
+
message: { type: graphql.GraphQLString }
|
|
762
|
+
}
|
|
763
|
+
}),
|
|
764
|
+
args: {
|
|
765
|
+
id: { type: new graphql.GraphQLNonNull(graphql.GraphQLString) }
|
|
766
|
+
},
|
|
767
|
+
resolve: async (_, args, context) => {
|
|
768
|
+
const access = await checkGraphQLAccess(collection, "delete", {
|
|
769
|
+
user: context.user,
|
|
770
|
+
req: context.req,
|
|
771
|
+
tenantID: context.tenantID,
|
|
772
|
+
apiKey: context.apiKey
|
|
773
|
+
});
|
|
774
|
+
if (!access.allowed) {
|
|
775
|
+
throw new Error("Access denied: cannot delete");
|
|
776
|
+
}
|
|
777
|
+
if (context.tenantID) {
|
|
778
|
+
db.setTenantContext({ tenantId: context.tenantID, userId: context.user?.id ?? "", role: context.user?.role, isSuperAdmin: context.user?.role === "super_admin" });
|
|
779
|
+
}
|
|
780
|
+
const doc = await db.delete({
|
|
781
|
+
collection: collection.slug,
|
|
782
|
+
id: args.id,
|
|
783
|
+
tenantID: context.tenantID
|
|
784
|
+
});
|
|
785
|
+
return { doc, message: "Deleted successfully" };
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
for (const global of globals) {
|
|
790
|
+
const inputType = globalInputTypes[global.slug];
|
|
791
|
+
if (!inputType) continue;
|
|
792
|
+
mutationFields[`update${toPascal(global.slug)}`] = {
|
|
793
|
+
type: new graphql.GraphQLObjectType({
|
|
794
|
+
name: `${toPascal(global.slug)}UpdateResult`,
|
|
795
|
+
fields: {
|
|
796
|
+
doc: { type: globalTypes[global.slug] || graphql.GraphQLString },
|
|
797
|
+
message: { type: graphql.GraphQLString }
|
|
798
|
+
}
|
|
799
|
+
}),
|
|
800
|
+
args: {
|
|
801
|
+
data: { type: new graphql.GraphQLNonNull(inputType) }
|
|
802
|
+
},
|
|
803
|
+
resolve: async (_, args, context) => {
|
|
804
|
+
const access = await checkGlobalAccess(global, "update", {
|
|
805
|
+
user: context.user,
|
|
806
|
+
req: context.req,
|
|
807
|
+
tenantID: context.tenantID,
|
|
808
|
+
apiKey: context.apiKey
|
|
809
|
+
});
|
|
810
|
+
if (!access.allowed) {
|
|
811
|
+
throw new Error("Access denied: cannot update global");
|
|
812
|
+
}
|
|
813
|
+
if (context.tenantID) {
|
|
814
|
+
db.setTenantContext({ tenantId: context.tenantID, userId: context.user?.id ?? "", role: context.user?.role, isSuperAdmin: context.user?.role === "super_admin" });
|
|
815
|
+
}
|
|
816
|
+
const doc = await db.findOne({
|
|
817
|
+
collection: `_globals_${global.slug}`,
|
|
818
|
+
where: {},
|
|
819
|
+
tenantID: context.tenantID
|
|
820
|
+
});
|
|
821
|
+
let result;
|
|
822
|
+
if (doc) {
|
|
823
|
+
result = await db.update({
|
|
824
|
+
collection: `_globals_${global.slug}`,
|
|
825
|
+
id: doc.id,
|
|
826
|
+
data: args.data,
|
|
827
|
+
tenantID: context.tenantID
|
|
828
|
+
});
|
|
829
|
+
} else {
|
|
830
|
+
result = await db.create({
|
|
831
|
+
collection: `_globals_${global.slug}`,
|
|
832
|
+
data: args.data,
|
|
833
|
+
tenantID: context.tenantID
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
return { doc: result, message: "Updated successfully" };
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
const Mutation = new graphql.GraphQLObjectType({
|
|
841
|
+
name: "Mutation",
|
|
842
|
+
fields: mutationFields
|
|
843
|
+
});
|
|
844
|
+
return new graphql.GraphQLSchema({
|
|
845
|
+
query: Query,
|
|
846
|
+
mutation: Mutation
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
function createGraphQLSchema(registry, db, options) {
|
|
850
|
+
return buildGraphQLSchema({
|
|
851
|
+
registry,
|
|
852
|
+
db,
|
|
853
|
+
user: options?.user,
|
|
854
|
+
req: options?.req,
|
|
855
|
+
tenantID: options?.tenantID,
|
|
856
|
+
apiKey: options?.apiKey
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
Object.defineProperty(exports, "printSchema", {
|
|
861
|
+
enumerable: true,
|
|
862
|
+
get: function () { return graphql.printSchema; }
|
|
863
|
+
});
|
|
864
|
+
exports.RelationLoader = RelationLoader;
|
|
865
|
+
exports.buildGraphQLSchema = buildGraphQLSchema;
|
|
866
|
+
exports.createGraphQLSchema = createGraphQLSchema;
|
|
867
|
+
exports.depthLimit = depthLimit;
|
|
868
|
+
//# sourceMappingURL=chunk-N4GN6Q3V.cjs.map
|
|
869
|
+
//# sourceMappingURL=chunk-N4GN6Q3V.cjs.map
|