@isardsat/editorial-server 6.13.2 → 6.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,16 @@ export declare function createSchema(configDirectory: string): Promise<Record<st
2
2
  displayName: string;
3
3
  fields: Record<string, {
4
4
  [x: string]: unknown;
5
- type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select";
5
+ type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select" | "multiselect";
6
6
  displayName: string;
7
7
  optional: boolean;
8
8
  displayExtra?: string | undefined;
9
9
  placeholder?: string | undefined;
10
10
  showInSummary?: boolean | undefined;
11
+ options?: string[] | undefined;
12
+ maxSelectedOptions?: number | undefined;
13
+ minSelectedOptions?: number | undefined;
14
+ isUploadedFile?: boolean | undefined;
11
15
  }>;
12
16
  filterBy?: string | undefined;
13
17
  singleton?: boolean | undefined;
@@ -4,12 +4,16 @@ export declare function createStorage(dataDirectory: string): {
4
4
  displayName: string;
5
5
  fields: Record<string, {
6
6
  [x: string]: unknown;
7
- type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select";
7
+ type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select" | "multiselect";
8
8
  displayName: string;
9
9
  optional: boolean;
10
10
  displayExtra?: string | undefined;
11
11
  placeholder?: string | undefined;
12
12
  showInSummary?: boolean | undefined;
13
+ options?: string[] | undefined;
14
+ maxSelectedOptions?: number | undefined;
15
+ minSelectedOptions?: number | undefined;
16
+ isUploadedFile?: boolean | undefined;
13
17
  }>;
14
18
  filterBy?: string | undefined;
15
19
  singleton?: boolean | undefined;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ type GenerateMetaSchemaOptions = {
2
+ /**
3
+ * Fields that exist in the Editorial's schema but should not be validated by the schema.
4
+ * These fields will be allowed without causing validation errors.
5
+ */
6
+ allowedExtraFields?: string[];
7
+ };
8
+ export declare function generateMetaSchema(options?: GenerateMetaSchemaOptions): {
9
+ $schema: string;
10
+ title: string;
11
+ type: string;
12
+ additionalProperties: {
13
+ $ref: string;
14
+ };
15
+ definitions: {
16
+ SchemaItem: {
17
+ type: string;
18
+ properties: Record<string, any>;
19
+ required: string[];
20
+ additionalProperties: boolean;
21
+ };
22
+ Field: {
23
+ type: string;
24
+ properties: Record<string, any>;
25
+ required: string[];
26
+ additionalProperties: boolean;
27
+ };
28
+ };
29
+ };
30
+ export {};
@@ -0,0 +1,105 @@
1
+ import { EditorialSchemaItemFieldSchema, EditorialSchemaItemFieldType, EditorialSchemaItemSchema, } from "@isardsat/editorial-common";
2
+ import { z } from "zod";
3
+ export function generateMetaSchema(options = {}) {
4
+ const { allowedExtraFields = [] } = options;
5
+ function generateDefinition(schema) {
6
+ const fieldShape = schema.shape;
7
+ const properties = {};
8
+ const required = [];
9
+ for (const key in fieldShape) {
10
+ const zodField = fieldShape[key];
11
+ // Handle the "type" field separately since it's an enum with specific options
12
+ if (key === "type") {
13
+ properties[key] = {
14
+ type: "string",
15
+ enum: EditorialSchemaItemFieldType.options,
16
+ };
17
+ required.push(key);
18
+ continue;
19
+ }
20
+ // Unwrap ALL wrappers to get the inner type
21
+ let innerField = zodField;
22
+ let isOptional = false;
23
+ let defaultValue = undefined;
24
+ // Loop to unwrap all layers (ZodDefault, ZodOptional, ZodNullable, etc.)
25
+ while (innerField) {
26
+ if (innerField instanceof z.ZodOptional) {
27
+ isOptional = true;
28
+ innerField = innerField.unwrap();
29
+ }
30
+ else if (innerField instanceof z.ZodDefault) {
31
+ isOptional = true; // Un camp amb default és efectivament opcional
32
+ defaultValue = innerField._def.defaultValue;
33
+ innerField = innerField._def.innerType;
34
+ }
35
+ else if (innerField instanceof z.ZodNullable) {
36
+ innerField = innerField.unwrap();
37
+ }
38
+ else {
39
+ // No more wrappers, exit loop
40
+ break;
41
+ }
42
+ }
43
+ // Check the actual base type
44
+ if (innerField instanceof z.ZodString) {
45
+ properties[key] = { type: "string" };
46
+ }
47
+ else if (innerField instanceof z.ZodBoolean) {
48
+ properties[key] = {
49
+ type: "boolean",
50
+ ...(defaultValue !== undefined && { default: defaultValue }),
51
+ };
52
+ }
53
+ else if (innerField instanceof z.ZodNumber) {
54
+ properties[key] = {
55
+ type: "number",
56
+ ...(defaultValue !== undefined && { default: defaultValue }),
57
+ };
58
+ }
59
+ else if (innerField instanceof z.ZodArray) {
60
+ properties[key] = { type: "array", items: { type: "string" } };
61
+ }
62
+ else if (innerField instanceof z.ZodEnum) {
63
+ properties[key] = {
64
+ type: "string",
65
+ enum: innerField.options,
66
+ };
67
+ }
68
+ else {
69
+ console.warn(`Unknown Zod type for field "${key}"`);
70
+ properties[key] = {};
71
+ }
72
+ // Add to required if not optional
73
+ if (!isOptional) {
74
+ required.push(key);
75
+ }
76
+ }
77
+ for (const field of allowedExtraFields) {
78
+ if (!(field in properties)) {
79
+ properties[field] = {}; // Allow any value
80
+ }
81
+ }
82
+ return {
83
+ type: "object",
84
+ properties,
85
+ required,
86
+ additionalProperties: false,
87
+ };
88
+ }
89
+ const fieldDefinition = generateDefinition(EditorialSchemaItemFieldSchema);
90
+ const schemaItemDefinition = generateDefinition(EditorialSchemaItemSchema);
91
+ schemaItemDefinition.properties.fields = {
92
+ type: "object",
93
+ additionalProperties: { $ref: "#/definitions/Field" },
94
+ };
95
+ return {
96
+ $schema: "http://json-schema.org/draft-07/schema#",
97
+ title: "Editorial Schema",
98
+ type: "object",
99
+ additionalProperties: { $ref: "#/definitions/SchemaItem" },
100
+ definitions: {
101
+ SchemaItem: schemaItemDefinition,
102
+ Field: fieldDefinition,
103
+ },
104
+ };
105
+ }
@@ -7,6 +7,7 @@ export function createActionRoutes(storage, hooks) {
7
7
  app.openapi(createRoute({
8
8
  method: "post",
9
9
  path: "/publish",
10
+ summary: "Trigger the publishing process",
10
11
  request: {
11
12
  body: {
12
13
  content: {
@@ -26,6 +27,7 @@ export function createActionRoutes(storage, hooks) {
26
27
  description: "Trigger the publishing process",
27
28
  },
28
29
  },
30
+ tags: ["Actions"],
29
31
  }),
30
32
  // TODO: Don't async, let the promises run in the background.
31
33
  async (c) => {
@@ -62,6 +64,7 @@ export function createActionRoutes(storage, hooks) {
62
64
  app.openapi(createRoute({
63
65
  method: "post",
64
66
  path: "/pull",
67
+ summary: "Trigger the pull process",
65
68
  request: {
66
69
  body: {
67
70
  content: {
@@ -76,6 +79,7 @@ export function createActionRoutes(storage, hooks) {
76
79
  description: "Trigger the pull process",
77
80
  },
78
81
  },
82
+ tags: ["Actions"],
79
83
  }), async (c) => {
80
84
  const { author } = c.req.valid("json");
81
85
  await hooks.onPull(author);
@@ -84,6 +88,7 @@ export function createActionRoutes(storage, hooks) {
84
88
  app.openapi(createRoute({
85
89
  method: "post",
86
90
  path: "/push",
91
+ summary: "Trigger the push process",
87
92
  request: {
88
93
  body: {
89
94
  content: {
@@ -98,6 +103,7 @@ export function createActionRoutes(storage, hooks) {
98
103
  description: "Trigger the push process",
99
104
  },
100
105
  },
106
+ tags: ["Actions"],
101
107
  }), async (c) => {
102
108
  const { author } = c.req.valid("json");
103
109
  await hooks.onPush(author);
@@ -1,8 +1,7 @@
1
1
  import { serveStatic } from "@hono/node-server/serve-static";
2
- import { OpenAPIHono } from "@hono/zod-openapi";
2
+ import { OpenAPIHono, z } from "@hono/zod-openapi";
3
3
  import { createRequire } from "node:module";
4
4
  import path from "path";
5
- import { z } from "@hono/zod-openapi";
6
5
  export function createAdminRoutes(config) {
7
6
  const app = new OpenAPIHono();
8
7
  // TODO: This is package manager dependent
@@ -20,7 +19,8 @@ export function createAdminRoutes(config) {
20
19
  content: {
21
20
  "application/json": {
22
21
  schema: z.object({
23
- firebase: z.object({
22
+ firebase: z
23
+ .object({
24
24
  apiKey: z.string(),
25
25
  authDomain: z.string(),
26
26
  databaseURL: z.string(),
@@ -28,13 +28,15 @@ export function createAdminRoutes(config) {
28
28
  storageBucket: z.string(),
29
29
  messagingSenderId: z.string(),
30
30
  dbUsersPath: z.string(),
31
- }).optional(),
31
+ })
32
+ .optional(),
32
33
  }),
33
34
  },
34
35
  },
35
36
  description: "Firebase configuration for admin",
36
37
  },
37
38
  },
39
+ tags: ["Admin"],
38
40
  }, (c) => {
39
41
  return c.json({ firebase: config.firebase });
40
42
  });
@@ -16,6 +16,7 @@ export function createConfigRoutes(config) {
16
16
  description: "Editorial configuration",
17
17
  },
18
18
  },
19
+ tags: ["Config"],
19
20
  }, (c) => {
20
21
  return c.json(config);
21
22
  });
@@ -1,5 +1,6 @@
1
1
  import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
2
- import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from "@isardsat/editorial-common";
2
+ import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, getOptionsReference, } from "@isardsat/editorial-common";
3
+ import { generateMetaSchema } from "../lib/utils/schema.js";
3
4
  function createCache() {
4
5
  let schemaCache = null;
5
6
  const contentCache = new Map();
@@ -36,6 +37,86 @@ function createCache() {
36
37
  },
37
38
  };
38
39
  }
40
+ /**
41
+ * Resolves uploaded file paths to full URLs for an item.
42
+ */
43
+ function resolveFileUrls(item, schema, itemType, origin) {
44
+ const resolvedItem = { ...item };
45
+ const itemSchema = schema[itemType];
46
+ if (!itemSchema)
47
+ return resolvedItem;
48
+ for (const [key, value] of Object.entries(resolvedItem)) {
49
+ if (!itemSchema.fields[key]?.isUploadedFile)
50
+ continue;
51
+ if (typeof value !== "string")
52
+ continue;
53
+ if (value.startsWith("http"))
54
+ continue;
55
+ resolvedItem[key] = `${origin}/${value}`;
56
+ }
57
+ return resolvedItem;
58
+ }
59
+ /**
60
+ * Resolves referenced fields in an item, replacing IDs with full objects.
61
+ * Recursively resolves nested references.
62
+ */
63
+ function resolveReferences(item, schema, itemType, content, origin, resolvedIds = new Set()) {
64
+ const itemIdentifier = `${itemType}:${item.id}`;
65
+ // Prevent circular references
66
+ if (resolvedIds.has(itemIdentifier)) {
67
+ return resolveFileUrls(item, schema, itemType, origin);
68
+ }
69
+ resolvedIds.add(itemIdentifier);
70
+ // First resolve file URLs for the current item
71
+ let resolvedItem = resolveFileUrls(item, schema, itemType, origin);
72
+ const itemSchema = schema[itemType];
73
+ if (!itemSchema)
74
+ return resolvedItem;
75
+ for (const [fieldKey, fieldConfig] of Object.entries(itemSchema.fields)) {
76
+ if (fieldConfig.type !== "select" && fieldConfig.type !== "multiselect") {
77
+ continue;
78
+ }
79
+ const referencedType = getOptionsReference(fieldConfig.options);
80
+ if (!referencedType)
81
+ continue;
82
+ const referencedCollection = content[referencedType];
83
+ if (!referencedCollection)
84
+ continue;
85
+ const fieldValue = item[fieldKey];
86
+ if (fieldConfig.type === "select" && typeof fieldValue === "string") {
87
+ // Single reference - replace ID with full object
88
+ const referencedItem = referencedCollection[fieldValue];
89
+ if (referencedItem) {
90
+ // Recursively resolve nested references
91
+ resolvedItem[fieldKey] = resolveReferences(referencedItem, schema, referencedType, content, origin, new Set(resolvedIds));
92
+ }
93
+ }
94
+ else if (fieldConfig.type === "multiselect" &&
95
+ Array.isArray(fieldValue)) {
96
+ // Multiple references - replace IDs with full objects
97
+ resolvedItem[fieldKey] = fieldValue
98
+ .map((id) => {
99
+ const referencedItem = referencedCollection[id];
100
+ if (!referencedItem)
101
+ return null;
102
+ // Recursively resolve nested references
103
+ return resolveReferences(referencedItem, schema, referencedType, content, origin, new Set(resolvedIds));
104
+ })
105
+ .filter(Boolean);
106
+ }
107
+ }
108
+ return resolvedItem;
109
+ }
110
+ /**
111
+ * Resolves all references in a collection.
112
+ */
113
+ function resolveCollectionReferences(collection, schema, itemType, content, origin) {
114
+ const resolvedCollection = {};
115
+ for (const [itemKey, item] of Object.entries(collection)) {
116
+ resolvedCollection[itemKey] = resolveReferences(item, schema, itemType, content, origin);
117
+ }
118
+ return resolvedCollection;
119
+ }
39
120
  export function createDataRoutes(config, storage) {
40
121
  const app = new OpenAPIHono();
41
122
  const cache = createCache();
@@ -43,6 +124,7 @@ export function createDataRoutes(config, storage) {
43
124
  app.openapi(createRoute({
44
125
  method: "get",
45
126
  path: "/schema",
127
+ summary: "Get Editorial schema",
46
128
  responses: {
47
129
  200: {
48
130
  content: {
@@ -53,6 +135,7 @@ export function createDataRoutes(config, storage) {
53
135
  description: "Get Editorial schema",
54
136
  },
55
137
  },
138
+ tags: ["Schema"],
56
139
  }), async (c) => {
57
140
  const schema = await cache.getSchema(storage);
58
141
  return c.json(schema);
@@ -60,6 +143,7 @@ export function createDataRoutes(config, storage) {
60
143
  app.openapi(createRoute({
61
144
  method: "get",
62
145
  path: "/data",
146
+ summary: "Get all Editorial data",
63
147
  request: {
64
148
  query: z.object({
65
149
  lang: z
@@ -70,6 +154,13 @@ export function createDataRoutes(config, storage) {
70
154
  example: "es_ES",
71
155
  }),
72
156
  preview: z.string().optional(),
157
+ resolve: z
158
+ .string()
159
+ .optional()
160
+ .openapi({
161
+ param: { name: "resolve", in: "query" },
162
+ description: "Resolve referenced fields to full objects",
163
+ }),
73
164
  }),
74
165
  },
75
166
  responses: {
@@ -82,14 +173,26 @@ export function createDataRoutes(config, storage) {
82
173
  description: "Get all Editorial data",
83
174
  },
84
175
  },
176
+ tags: ["Data"],
85
177
  }), async (c) => {
86
- const { preview } = c.req.valid("query");
178
+ const { preview, resolve } = c.req.valid("query");
87
179
  const content = await cache.getContent(storage, { production: !preview });
88
- return c.json(content);
180
+ if (!resolve) {
181
+ return c.json(content);
182
+ }
183
+ const origin = preview ? new URL(c.req.url).origin : publicFilesUrl;
184
+ // Resolve references for all collections
185
+ const schema = await cache.getSchema(storage);
186
+ const resolvedContent = {};
187
+ for (const [itemType, collection] of Object.entries(content)) {
188
+ resolvedContent[itemType] = resolveCollectionReferences(collection, schema, itemType, content, origin);
189
+ }
190
+ return c.json(resolvedContent);
89
191
  });
90
192
  app.openapi(createRoute({
91
193
  method: "get",
92
194
  path: "/data/{itemType}",
195
+ summary: "Get objects data by type",
93
196
  request: {
94
197
  params: z.object({
95
198
  itemType: z.string().openapi({
@@ -106,6 +209,13 @@ export function createDataRoutes(config, storage) {
106
209
  example: "es_ES",
107
210
  }),
108
211
  preview: z.string().optional(),
212
+ resolve: z
213
+ .string()
214
+ .optional()
215
+ .openapi({
216
+ param: { name: "resolve", in: "query" },
217
+ description: "Resolve referenced fields to full objects",
218
+ }),
109
219
  }),
110
220
  },
111
221
  responses: {
@@ -121,9 +231,10 @@ export function createDataRoutes(config, storage) {
121
231
  description: "Collection not found",
122
232
  },
123
233
  },
234
+ tags: ["Data"],
124
235
  }), async (c) => {
125
236
  const { itemType } = c.req.valid("param");
126
- const { lang, preview } = c.req.valid("query");
237
+ const { lang, preview, resolve } = c.req.valid("query");
127
238
  const origin = preview ? new URL(c.req.url).origin : publicFilesUrl;
128
239
  const content = await cache.getContent(storage, {
129
240
  production: !preview,
@@ -149,6 +260,11 @@ export function createDataRoutes(config, storage) {
149
260
  }
150
261
  }
151
262
  }
263
+ // Resolve references if requested (includes file URL resolution)
264
+ if (resolve) {
265
+ return c.json(resolveCollectionReferences(collection, schema, itemType, content, origin));
266
+ }
267
+ // Apply file URL resolution for non-resolved requests
152
268
  for (const [itemKey, itemValue] of Object.entries(collection)) {
153
269
  for (const [key, value] of Object.entries(itemValue)) {
154
270
  if (!schema[itemType].fields[key]?.isUploadedFile)
@@ -163,6 +279,7 @@ export function createDataRoutes(config, storage) {
163
279
  app.openapi(createRoute({
164
280
  method: "get",
165
281
  path: "/data/{itemType}/ids",
282
+ summary: "Get object ids by type",
166
283
  request: {
167
284
  params: z.object({
168
285
  itemType: z.string().openapi({
@@ -187,6 +304,7 @@ export function createDataRoutes(config, storage) {
187
304
  description: "Item not found",
188
305
  },
189
306
  },
307
+ tags: ["Data"],
190
308
  }), async (c) => {
191
309
  const { itemType } = c.req.valid("param");
192
310
  const { preview } = c.req.valid("query");
@@ -199,6 +317,7 @@ export function createDataRoutes(config, storage) {
199
317
  app.openapi(createRoute({
200
318
  method: "get",
201
319
  path: "/data/{itemType}/{id}",
320
+ summary: "Get object data by type and id",
202
321
  request: {
203
322
  params: z.object({
204
323
  itemType: z.string().openapi({
@@ -219,6 +338,13 @@ export function createDataRoutes(config, storage) {
219
338
  example: "es_ES",
220
339
  }),
221
340
  preview: z.string().optional(),
341
+ resolve: z
342
+ .string()
343
+ .optional()
344
+ .openapi({
345
+ param: { name: "resolve", in: "query" },
346
+ description: "Resolve referenced fields to full objects",
347
+ }),
222
348
  }),
223
349
  },
224
350
  responses: {
@@ -234,9 +360,10 @@ export function createDataRoutes(config, storage) {
234
360
  description: "Collection or item not found",
235
361
  },
236
362
  },
363
+ tags: ["Data"],
237
364
  }), async (c) => {
238
365
  const { itemType, id } = c.req.valid("param");
239
- const { lang, preview } = c.req.valid("query");
366
+ const { lang, preview, resolve } = c.req.valid("query");
240
367
  const origin = preview ? new URL(c.req.url).origin : publicFilesUrl;
241
368
  const content = await cache.getContent(storage, {
242
369
  production: !preview,
@@ -247,7 +374,7 @@ export function createDataRoutes(config, storage) {
247
374
  if (!collection) {
248
375
  return c.notFound();
249
376
  }
250
- const item = collection[id];
377
+ let item = collection[id];
251
378
  if (!item) {
252
379
  return c.notFound();
253
380
  }
@@ -261,6 +388,12 @@ export function createDataRoutes(config, storage) {
261
388
  }
262
389
  }
263
390
  }
391
+ // Resolve references if requested (includes file URL resolution)
392
+ if (resolve) {
393
+ item = resolveReferences(item, schema, itemType, content, origin);
394
+ return c.json(item);
395
+ }
396
+ // Apply file URL resolution for non-resolved requests
264
397
  for (const [key, value] of Object.entries(item)) {
265
398
  if (!schema[itemType].fields[key]?.isUploadedFile)
266
399
  continue;
@@ -273,6 +406,7 @@ export function createDataRoutes(config, storage) {
273
406
  app.openapi(createRoute({
274
407
  method: "put",
275
408
  path: "/data/{itemType}/{id}",
409
+ summary: "Create or update object data by type and id",
276
410
  request: {
277
411
  params: z.object({
278
412
  itemType: z.string().openapi({
@@ -303,6 +437,7 @@ export function createDataRoutes(config, storage) {
303
437
  description: "Create a new object",
304
438
  },
305
439
  },
440
+ tags: ["Data"],
306
441
  }), async (c) => {
307
442
  const itemAtts = await c.req.json();
308
443
  const newItem = await storage.createItem(itemAtts);
@@ -312,6 +447,7 @@ export function createDataRoutes(config, storage) {
312
447
  app.openapi(createRoute({
313
448
  method: "patch",
314
449
  path: "/data/{itemType}/{id}",
450
+ summary: "Update object data by type and id",
315
451
  request: {
316
452
  params: z.object({
317
453
  itemType: z.string().openapi({
@@ -342,6 +478,7 @@ export function createDataRoutes(config, storage) {
342
478
  description: "Update object",
343
479
  },
344
480
  },
481
+ tags: ["Data"],
345
482
  }), async (c) => {
346
483
  const itemAtts = await c.req.json();
347
484
  const newItem = await storage.updateItem(itemAtts);
@@ -351,6 +488,7 @@ export function createDataRoutes(config, storage) {
351
488
  app.openapi(createRoute({
352
489
  method: "delete",
353
490
  path: "/data/{itemType}/{id}",
491
+ summary: "Delete object by type and id",
354
492
  request: {
355
493
  params: z.object({
356
494
  itemType: z.string().openapi({
@@ -373,11 +511,47 @@ export function createDataRoutes(config, storage) {
373
511
  description: "Delete object",
374
512
  },
375
513
  },
514
+ tags: ["Data"],
376
515
  }), async (c) => {
377
516
  const { itemType, id } = c.req.valid("param");
378
517
  await storage.deleteItem({ type: itemType, id });
379
518
  cache.invalidateContent();
380
519
  return c.json(true, 200);
381
520
  });
521
+ app.openapi(createRoute({
522
+ method: "get",
523
+ path: "/meta-schema",
524
+ summary: "Get Editorial meta-schema",
525
+ request: {
526
+ query: z.object({
527
+ allowedExtraFields: z
528
+ .string()
529
+ .optional()
530
+ .openapi({
531
+ param: { name: "allowedExtraFields", in: "query" },
532
+ example: "customField,legacyField",
533
+ description: "Comma-separated list of extra field names that exist in the Editorial's schema but should not cause validation errors",
534
+ }),
535
+ }),
536
+ },
537
+ responses: {
538
+ 200: {
539
+ content: {
540
+ "application/json": {
541
+ schema: z.record(z.string(), z.any()),
542
+ },
543
+ },
544
+ description: "JSON Schema meta-schema for the Editorial configuration",
545
+ },
546
+ },
547
+ tags: ["Schema"],
548
+ }), async (c) => {
549
+ const { allowedExtraFields: allowedExtraFieldsParam } = c.req.valid("query");
550
+ const allowedExtraFields = allowedExtraFieldsParam
551
+ ? allowedExtraFieldsParam.split(",").map((f) => f.trim())
552
+ : [];
553
+ const metaSchema = generateMetaSchema({ allowedExtraFields });
554
+ return c.json(metaSchema);
555
+ });
382
556
  return app;
383
557
  }
@@ -14,6 +14,7 @@ export async function createFilesRoutes(config) {
14
14
  app.openapi(createRoute({
15
15
  method: "get",
16
16
  path: "/files",
17
+ summary: "Get tree of public files with total size",
17
18
  request: {
18
19
  query: z.object({
19
20
  preview: z.string().optional(),
@@ -29,6 +30,7 @@ export async function createFilesRoutes(config) {
29
30
  description: "Get tree of public files with total size",
30
31
  },
31
32
  },
33
+ tags: ["Files"],
32
34
  }),
33
35
  // TODO: Index large files from bucket.
34
36
  async (c) => {
@@ -109,6 +111,7 @@ export async function createFilesRoutes(config) {
109
111
  app.openapi(createRoute({
110
112
  method: "delete",
111
113
  path: "/files",
114
+ summary: "Delete a file or directory (moved to deleted folder)",
112
115
  request: {
113
116
  body: {
114
117
  content: {
@@ -146,6 +149,7 @@ export async function createFilesRoutes(config) {
146
149
  description: "Server error",
147
150
  },
148
151
  },
152
+ tags: ["Files"],
149
153
  }), async (c) => {
150
154
  const { path: relativePathInput } = c.req.valid("json");
151
155
  try {
@@ -174,6 +178,7 @@ export async function createFilesRoutes(config) {
174
178
  app.openapi(createRoute({
175
179
  method: "put",
176
180
  path: "/files",
181
+ summary: "Upload files to a specified directory",
177
182
  request: {
178
183
  body: {
179
184
  content: {
@@ -217,6 +222,7 @@ export async function createFilesRoutes(config) {
217
222
  description: "Server error",
218
223
  },
219
224
  },
225
+ tags: ["Files"],
220
226
  }), async (c) => {
221
227
  try {
222
228
  const body = await c.req.parseBody();
@@ -257,6 +263,7 @@ export async function createFilesRoutes(config) {
257
263
  app.openapi(createRoute({
258
264
  method: "post",
259
265
  path: "/files/directory",
266
+ summary: "Create a new directory",
260
267
  request: {
261
268
  body: {
262
269
  content: {
@@ -314,6 +321,7 @@ export async function createFilesRoutes(config) {
314
321
  description: "Server error",
315
322
  },
316
323
  },
324
+ tags: ["Files"],
317
325
  }), async (c) => {
318
326
  try {
319
327
  const { path, name } = c.req.valid("json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isardsat/editorial-server",
3
- "version": "6.13.2",
3
+ "version": "6.15.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,8 +14,8 @@
14
14
  "hono": "^4.9.8",
15
15
  "yaml": "^2.8.1",
16
16
  "zod": "^4.1.11",
17
- "@isardsat/editorial-admin": "^6.13.2",
18
- "@isardsat/editorial-common": "^6.13.2"
17
+ "@isardsat/editorial-admin": "^6.15.0",
18
+ "@isardsat/editorial-common": "^6.15.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@tsconfig/node22": "^22.0.0",