@isardsat/editorial-server 6.14.0 → 6.16.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.
@@ -11,6 +11,7 @@ export declare function createSchema(configDirectory: string): Promise<Record<st
11
11
  options?: string[] | undefined;
12
12
  maxSelectedOptions?: number | undefined;
13
13
  minSelectedOptions?: number | undefined;
14
+ isUploadedFile?: boolean | undefined;
14
15
  }>;
15
16
  filterBy?: string | undefined;
16
17
  singleton?: boolean | undefined;
@@ -13,6 +13,7 @@ export declare function createStorage(dataDirectory: string): {
13
13
  options?: string[] | undefined;
14
14
  maxSelectedOptions?: number | undefined;
15
15
  minSelectedOptions?: number | undefined;
16
+ isUploadedFile?: boolean | undefined;
16
17
  }>;
17
18
  filterBy?: string | undefined;
18
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
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();
@@ -123,6 +124,7 @@ export function createDataRoutes(config, storage) {
123
124
  app.openapi(createRoute({
124
125
  method: "get",
125
126
  path: "/schema",
127
+ summary: "Get Editorial schema",
126
128
  responses: {
127
129
  200: {
128
130
  content: {
@@ -133,6 +135,7 @@ export function createDataRoutes(config, storage) {
133
135
  description: "Get Editorial schema",
134
136
  },
135
137
  },
138
+ tags: ["Schema"],
136
139
  }), async (c) => {
137
140
  const schema = await cache.getSchema(storage);
138
141
  return c.json(schema);
@@ -140,6 +143,7 @@ export function createDataRoutes(config, storage) {
140
143
  app.openapi(createRoute({
141
144
  method: "get",
142
145
  path: "/data",
146
+ summary: "Get all Editorial data",
143
147
  request: {
144
148
  query: z.object({
145
149
  lang: z
@@ -169,6 +173,7 @@ export function createDataRoutes(config, storage) {
169
173
  description: "Get all Editorial data",
170
174
  },
171
175
  },
176
+ tags: ["Data"],
172
177
  }), async (c) => {
173
178
  const { preview, resolve } = c.req.valid("query");
174
179
  const content = await cache.getContent(storage, { production: !preview });
@@ -187,6 +192,7 @@ export function createDataRoutes(config, storage) {
187
192
  app.openapi(createRoute({
188
193
  method: "get",
189
194
  path: "/data/{itemType}",
195
+ summary: "Get objects data by type",
190
196
  request: {
191
197
  params: z.object({
192
198
  itemType: z.string().openapi({
@@ -225,6 +231,7 @@ export function createDataRoutes(config, storage) {
225
231
  description: "Collection not found",
226
232
  },
227
233
  },
234
+ tags: ["Data"],
228
235
  }), async (c) => {
229
236
  const { itemType } = c.req.valid("param");
230
237
  const { lang, preview, resolve } = c.req.valid("query");
@@ -272,6 +279,7 @@ export function createDataRoutes(config, storage) {
272
279
  app.openapi(createRoute({
273
280
  method: "get",
274
281
  path: "/data/{itemType}/ids",
282
+ summary: "Get object ids by type",
275
283
  request: {
276
284
  params: z.object({
277
285
  itemType: z.string().openapi({
@@ -296,6 +304,7 @@ export function createDataRoutes(config, storage) {
296
304
  description: "Item not found",
297
305
  },
298
306
  },
307
+ tags: ["Data"],
299
308
  }), async (c) => {
300
309
  const { itemType } = c.req.valid("param");
301
310
  const { preview } = c.req.valid("query");
@@ -308,6 +317,7 @@ export function createDataRoutes(config, storage) {
308
317
  app.openapi(createRoute({
309
318
  method: "get",
310
319
  path: "/data/{itemType}/{id}",
320
+ summary: "Get object data by type and id",
311
321
  request: {
312
322
  params: z.object({
313
323
  itemType: z.string().openapi({
@@ -350,6 +360,7 @@ export function createDataRoutes(config, storage) {
350
360
  description: "Collection or item not found",
351
361
  },
352
362
  },
363
+ tags: ["Data"],
353
364
  }), async (c) => {
354
365
  const { itemType, id } = c.req.valid("param");
355
366
  const { lang, preview, resolve } = c.req.valid("query");
@@ -395,6 +406,7 @@ export function createDataRoutes(config, storage) {
395
406
  app.openapi(createRoute({
396
407
  method: "put",
397
408
  path: "/data/{itemType}/{id}",
409
+ summary: "Create or update object data by type and id",
398
410
  request: {
399
411
  params: z.object({
400
412
  itemType: z.string().openapi({
@@ -425,6 +437,7 @@ export function createDataRoutes(config, storage) {
425
437
  description: "Create a new object",
426
438
  },
427
439
  },
440
+ tags: ["Data"],
428
441
  }), async (c) => {
429
442
  const itemAtts = await c.req.json();
430
443
  const newItem = await storage.createItem(itemAtts);
@@ -434,6 +447,7 @@ export function createDataRoutes(config, storage) {
434
447
  app.openapi(createRoute({
435
448
  method: "patch",
436
449
  path: "/data/{itemType}/{id}",
450
+ summary: "Update object data by type and id",
437
451
  request: {
438
452
  params: z.object({
439
453
  itemType: z.string().openapi({
@@ -464,6 +478,7 @@ export function createDataRoutes(config, storage) {
464
478
  description: "Update object",
465
479
  },
466
480
  },
481
+ tags: ["Data"],
467
482
  }), async (c) => {
468
483
  const itemAtts = await c.req.json();
469
484
  const newItem = await storage.updateItem(itemAtts);
@@ -473,6 +488,7 @@ export function createDataRoutes(config, storage) {
473
488
  app.openapi(createRoute({
474
489
  method: "delete",
475
490
  path: "/data/{itemType}/{id}",
491
+ summary: "Delete object by type and id",
476
492
  request: {
477
493
  params: z.object({
478
494
  itemType: z.string().openapi({
@@ -495,11 +511,47 @@ export function createDataRoutes(config, storage) {
495
511
  description: "Delete object",
496
512
  },
497
513
  },
514
+ tags: ["Data"],
498
515
  }), async (c) => {
499
516
  const { itemType, id } = c.req.valid("param");
500
517
  await storage.deleteItem({ type: itemType, id });
501
518
  cache.invalidateContent();
502
519
  return c.json(true, 200);
503
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
+ });
504
556
  return app;
505
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.14.0",
3
+ "version": "6.16.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-common": "^6.14.0",
18
- "@isardsat/editorial-admin": "^6.14.0"
17
+ "@isardsat/editorial-admin": "^6.16.0",
18
+ "@isardsat/editorial-common": "^6.16.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@tsconfig/node22": "^22.0.0",