@isardsat/editorial-server 6.0.2 → 6.0.4

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/app.js CHANGED
@@ -7,6 +7,7 @@ import { createHooks } from "./lib/hooks.js";
7
7
  import { createStorage } from "./lib/storage.js";
8
8
  import { createActionRoutes } from "./routes/actions.js";
9
9
  import { createAdminRoutes } from "./routes/admin.js";
10
+ import { createConfigRoutes } from "./routes/config.js";
10
11
  import { createDataRoutes } from "./routes/data.js";
11
12
  import { createFilesRoutes } from "./routes/files.js";
12
13
  export const BASE_EDITORIAL_PATH = "./editorial";
@@ -16,6 +17,7 @@ export async function createEditorialServer({ configDirectory = BASE_EDITORIAL_P
16
17
  const storage = createStorage(editorialDirectory);
17
18
  const hooks = await createHooks(configDirectory);
18
19
  app.use(logger());
20
+ app.route("/api/v1", createConfigRoutes(config));
19
21
  app.route("/api/v1", createDataRoutes(storage));
20
22
  app.route("/api/v1", createFilesRoutes());
21
23
  app.route("/api/v1", createActionRoutes(storage, hooks));
@@ -2,7 +2,7 @@ export declare function createHooks(configDirectory: string): Promise<{
2
2
  onPublish: (content: any, schema: any) => Promise<any>;
3
3
  onLocalize: (content: any, schema: any) => Promise<any>;
4
4
  onLocalizeEnd: (content: any, schema: any) => Promise<any>;
5
- onPull: () => Promise<any>;
6
- onPush: () => Promise<any>;
5
+ onPull: (author: string) => Promise<any>;
6
+ onPush: (author: string) => Promise<any>;
7
7
  }>;
8
8
  export type Hooks = Awaited<ReturnType<typeof createHooks>>;
package/dist/lib/hooks.js CHANGED
@@ -1,10 +1,10 @@
1
- import { join } from 'node:path';
1
+ import { join } from "node:path";
2
2
  export async function createHooks(configDirectory) {
3
- const hooksDirPath = join(configDirectory, 'hooks');
3
+ const hooksDirPath = join(configDirectory, "hooks");
4
4
  async function loadHook(name) {
5
5
  const hookScript = join(process.cwd(), hooksDirPath, name);
6
6
  const module = await import(`${hookScript}.mjs`);
7
- const hookFunction = typeof module.default === 'function' ? module.default : null;
7
+ const hookFunction = typeof module.default === "function" ? module.default : null;
8
8
  return hookFunction;
9
9
  }
10
10
  async function executeHook(hookName, ...args) {
@@ -14,19 +14,27 @@ export async function createHooks(configDirectory) {
14
14
  return hook(...args);
15
15
  }
16
16
  async function onPublish(content, schema) {
17
- return executeHook('onPublish', content, schema);
17
+ return executeHook("onPublish", content, schema);
18
18
  }
19
19
  async function onLocalize(content, schema) {
20
- return executeHook('onLocalize', content, schema);
20
+ return executeHook("onLocalize", content, schema);
21
21
  }
22
22
  async function onLocalizeEnd(content, schema) {
23
- return executeHook('onLocalizeEnd', content, schema);
23
+ return executeHook("onLocalizeEnd", content, schema);
24
24
  }
25
- async function onPull() {
26
- return executeHook('onPull');
25
+ async function onPull(author) {
26
+ if (process.env.NODE_ENV !== "production") {
27
+ console.info("Dropping onPull hook event in development environment");
28
+ return;
29
+ }
30
+ return executeHook("onPull", author);
27
31
  }
28
- async function onPush() {
29
- return executeHook('onPush');
32
+ async function onPush(author) {
33
+ if (process.env.NODE_ENV !== "production") {
34
+ console.info("Dropping onPush hook event in development environment");
35
+ return;
36
+ }
37
+ return executeHook("onPush", author);
30
38
  }
31
39
  return { onPublish, onLocalize, onLocalizeEnd, onPull, onPush };
32
40
  }
@@ -0,0 +1,35 @@
1
+ import type { EditorialData, EditorialDataObjectWithType } from "@isardsat/editorial-common";
2
+ export declare function createI18n(dataDirectory: string): {
3
+ getSchema: () => Promise<Record<string, {
4
+ displayName: string;
5
+ fields: Record<string, import("zod").objectOutputType<{
6
+ type: import("zod").ZodEnum<["string", "url", "boolean", "date", "datetime", "markdown", "number", "color", "select", "color"]>;
7
+ displayName: import("zod").ZodString;
8
+ displayExtra: import("zod").ZodOptional<import("zod").ZodString>;
9
+ placeholder: import("zod").ZodOptional<import("zod").ZodString>;
10
+ isRequired: import("zod").ZodOptional<import("zod").ZodBoolean>;
11
+ showInSummary: import("zod").ZodOptional<import("zod").ZodBoolean>;
12
+ }, import("zod").ZodTypeAny, "passthrough">>;
13
+ singleton?: boolean | undefined;
14
+ }>>;
15
+ getContent: () => Promise<EditorialData>;
16
+ saveLocalisationMessages: (messages: any) => Promise<boolean>;
17
+ createItem: (item: EditorialDataObjectWithType) => Promise<import("zod").objectOutputType<{
18
+ id: import("zod").ZodString;
19
+ type: import("zod").ZodString;
20
+ }, import("zod").ZodTypeAny, "passthrough">>;
21
+ updateItem: (item: EditorialDataObjectWithType) => Promise<import("zod").objectOutputType<{
22
+ id: import("zod").ZodString;
23
+ createdAt: import("zod").ZodDefault<import("zod").ZodString>;
24
+ updatedAt: import("zod").ZodDefault<import("zod").ZodString>;
25
+ }, import("zod").ZodTypeAny, "passthrough">>;
26
+ deleteItem: (item: EditorialDataObjectWithType) => Promise<Record<string, Record<string, import("zod").objectOutputType<{
27
+ id: import("zod").ZodString;
28
+ createdAt: import("zod").ZodDefault<import("zod").ZodString>;
29
+ updatedAt: import("zod").ZodDefault<import("zod").ZodString>;
30
+ }, import("zod").ZodTypeAny, "passthrough">>>>;
31
+ saveContent: ({ production }: {
32
+ production?: boolean;
33
+ }) => Promise<boolean>;
34
+ };
35
+ export type Storage = ReturnType<typeof createStorage>;
@@ -0,0 +1,67 @@
1
+ import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from "@isardsat/editorial-common";
2
+ import { readFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { parse } from "yaml";
5
+ import { writeFileSafe } from "./utils/fs.js";
6
+ export function createI18n(dataDirectory) {
7
+ const schemaPath = join(dataDirectory, "schema.yaml");
8
+ const dataPath = join(dataDirectory, "data.json");
9
+ const dataProdPath = join(dataDirectory, "data.prod.json");
10
+ const dataExtractedPath = join(dataDirectory, "data.messages.json");
11
+ async function getSchema() {
12
+ const schemaFile = await readFile(schemaPath, "utf-8").then((value) => parse(value));
13
+ const schema = EditorialSchemaSchema.parse(schemaFile);
14
+ return schema;
15
+ }
16
+ /**
17
+ * TODO: This should ideally cache the result of reading the file until an update occurs.
18
+ */
19
+ async function getContent() {
20
+ return await readFile(dataPath, "utf-8").then((value) => JSON.parse(value));
21
+ }
22
+ async function saveContent({ production }) {
23
+ const content = await getContent();
24
+ await writeFileSafe(production ? dataProdPath : dataPath, JSON.stringify(EditorialDataSchema.parse(content), null, 2));
25
+ return true;
26
+ }
27
+ async function saveLocalisationMessages(messages) {
28
+ await writeFileSafe(dataExtractedPath, JSON.stringify(messages, null, 2));
29
+ return true;
30
+ }
31
+ async function createItem(item) {
32
+ const content = await getContent();
33
+ content[item.type][item.id] = EditorialDataItemSchema.parse(item);
34
+ // TODO: Use superjson to safely encode different types.
35
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
36
+ return item;
37
+ }
38
+ async function updateItem(item) {
39
+ const content = await getContent();
40
+ const oldItem = content[item.type][item.id];
41
+ const newItem = EditorialDataItemSchema.parse({
42
+ ...oldItem,
43
+ ...item,
44
+ updatedAt: new Date().toISOString(),
45
+ });
46
+ content[item.type][item.id] = newItem;
47
+ // TODO: Use superjson to safely encode different types.
48
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
49
+ return newItem;
50
+ }
51
+ async function deleteItem(item) {
52
+ const content = await getContent();
53
+ delete content[item.type][item.id];
54
+ // TODO: Use superjson to safely encode different types.
55
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
56
+ return content;
57
+ }
58
+ return {
59
+ getSchema,
60
+ getContent,
61
+ saveLocalisationMessages,
62
+ createItem,
63
+ updateItem,
64
+ deleteItem,
65
+ saveContent,
66
+ };
67
+ }
@@ -0,0 +1,35 @@
1
+ import type { EditorialData, EditorialDataObjectWithType } from "@isardsat/editorial-common";
2
+ export declare function createStorage(dataDirectory: string): {
3
+ getSchema: () => Promise<Record<string, {
4
+ displayName: string;
5
+ fields: Record<string, import("zod").objectOutputType<{
6
+ type: import("zod").ZodEnum<["string", "url", "boolean", "date", "datetime", "markdown", "number", "color", "select", "color"]>;
7
+ displayName: import("zod").ZodString;
8
+ displayExtra: import("zod").ZodOptional<import("zod").ZodString>;
9
+ placeholder: import("zod").ZodOptional<import("zod").ZodString>;
10
+ isRequired: import("zod").ZodOptional<import("zod").ZodBoolean>;
11
+ showInSummary: import("zod").ZodOptional<import("zod").ZodBoolean>;
12
+ }, import("zod").ZodTypeAny, "passthrough">>;
13
+ singleton?: boolean | undefined;
14
+ }>>;
15
+ getContent: () => Promise<EditorialData>;
16
+ saveLocalisationMessages: (messages: any) => Promise<boolean>;
17
+ createItem: (item: EditorialDataObjectWithType) => Promise<import("zod").objectOutputType<{
18
+ id: import("zod").ZodString;
19
+ type: import("zod").ZodString;
20
+ }, import("zod").ZodTypeAny, "passthrough">>;
21
+ updateItem: (item: EditorialDataObjectWithType) => Promise<import("zod").objectOutputType<{
22
+ id: import("zod").ZodString;
23
+ createdAt: import("zod").ZodDefault<import("zod").ZodString>;
24
+ updatedAt: import("zod").ZodDefault<import("zod").ZodString>;
25
+ }, import("zod").ZodTypeAny, "passthrough">>;
26
+ deleteItem: (item: EditorialDataObjectWithType) => Promise<Record<string, Record<string, import("zod").objectOutputType<{
27
+ id: import("zod").ZodString;
28
+ createdAt: import("zod").ZodDefault<import("zod").ZodString>;
29
+ updatedAt: import("zod").ZodDefault<import("zod").ZodString>;
30
+ }, import("zod").ZodTypeAny, "passthrough">>>>;
31
+ saveContent: ({ production }: {
32
+ production?: boolean;
33
+ }) => Promise<boolean>;
34
+ };
35
+ export type Storage = ReturnType<typeof createStorage>;
@@ -0,0 +1,67 @@
1
+ import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from "@isardsat/editorial-common";
2
+ import { readFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { parse } from "yaml";
5
+ import { writeFileSafe } from "./utils/fs.js";
6
+ export function createStorage(dataDirectory) {
7
+ const schemaPath = join(dataDirectory, "schema.yaml");
8
+ const dataPath = join(dataDirectory, "data.json");
9
+ const dataProdPath = join(dataDirectory, "data.prod.json");
10
+ const dataExtractedPath = join(dataDirectory, "data.messages.json");
11
+ async function getSchema() {
12
+ const schemaFile = await readFile(schemaPath, "utf-8").then((value) => parse(value));
13
+ const schema = EditorialSchemaSchema.parse(schemaFile);
14
+ return schema;
15
+ }
16
+ /**
17
+ * TODO: This should ideally cache the result of reading the file until an update occurs.
18
+ */
19
+ async function getContent() {
20
+ return await readFile(dataPath, "utf-8").then((value) => JSON.parse(value));
21
+ }
22
+ async function saveContent({ production }) {
23
+ const content = await getContent();
24
+ await writeFileSafe(production ? dataProdPath : dataPath, JSON.stringify(EditorialDataSchema.parse(content), null, 2));
25
+ return true;
26
+ }
27
+ async function saveLocalisationMessages(messages) {
28
+ await writeFileSafe(dataExtractedPath, JSON.stringify(messages, null, 2));
29
+ return true;
30
+ }
31
+ async function createItem(item) {
32
+ const content = await getContent();
33
+ content[item.type][item.id] = EditorialDataItemSchema.parse(item);
34
+ // TODO: Use superjson to safely encode different types.
35
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
36
+ return item;
37
+ }
38
+ async function updateItem(item) {
39
+ const content = await getContent();
40
+ const oldItem = content[item.type][item.id];
41
+ const newItem = EditorialDataItemSchema.parse({
42
+ ...oldItem,
43
+ ...item,
44
+ updatedAt: new Date().toISOString(),
45
+ });
46
+ content[item.type][item.id] = newItem;
47
+ // TODO: Use superjson to safely encode different types.
48
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
49
+ return newItem;
50
+ }
51
+ async function deleteItem(item) {
52
+ const content = await getContent();
53
+ delete content[item.type][item.id];
54
+ // TODO: Use superjson to safely encode different types.
55
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
56
+ return content;
57
+ }
58
+ return {
59
+ getSchema,
60
+ getContent,
61
+ saveLocalisationMessages,
62
+ createItem,
63
+ updateItem,
64
+ deleteItem,
65
+ saveContent,
66
+ };
67
+ }
@@ -0,0 +1,35 @@
1
+ import type { EditorialData, EditorialDataObjectWithType } from "@isardsat/editorial-common";
2
+ export declare function createStorage(dataDirectory: string): {
3
+ getSchema: () => Promise<Record<string, {
4
+ displayName: string;
5
+ fields: Record<string, import("zod").objectOutputType<{
6
+ type: import("zod").ZodEnum<["string", "url", "boolean", "date", "datetime", "markdown", "number", "color", "select", "color"]>;
7
+ displayName: import("zod").ZodString;
8
+ displayExtra: import("zod").ZodOptional<import("zod").ZodString>;
9
+ placeholder: import("zod").ZodOptional<import("zod").ZodString>;
10
+ isRequired: import("zod").ZodOptional<import("zod").ZodBoolean>;
11
+ showInSummary: import("zod").ZodOptional<import("zod").ZodBoolean>;
12
+ }, import("zod").ZodTypeAny, "passthrough">>;
13
+ singleton?: boolean | undefined;
14
+ }>>;
15
+ getContent: () => Promise<EditorialData>;
16
+ saveLocalisationMessages: (messages: any) => Promise<boolean>;
17
+ createItem: (item: EditorialDataObjectWithType) => Promise<import("zod").objectOutputType<{
18
+ id: import("zod").ZodString;
19
+ type: import("zod").ZodString;
20
+ }, import("zod").ZodTypeAny, "passthrough">>;
21
+ updateItem: (item: EditorialDataObjectWithType) => Promise<import("zod").objectOutputType<{
22
+ id: import("zod").ZodString;
23
+ createdAt: import("zod").ZodDefault<import("zod").ZodString>;
24
+ updatedAt: import("zod").ZodDefault<import("zod").ZodString>;
25
+ }, import("zod").ZodTypeAny, "passthrough">>;
26
+ deleteItem: (item: EditorialDataObjectWithType) => Promise<Record<string, Record<string, import("zod").objectOutputType<{
27
+ id: import("zod").ZodString;
28
+ createdAt: import("zod").ZodDefault<import("zod").ZodString>;
29
+ updatedAt: import("zod").ZodDefault<import("zod").ZodString>;
30
+ }, import("zod").ZodTypeAny, "passthrough">>>>;
31
+ saveContent: ({ production }: {
32
+ production?: boolean;
33
+ }) => Promise<boolean>;
34
+ };
35
+ export type Storage = ReturnType<typeof createStorage>;
@@ -0,0 +1,67 @@
1
+ import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from "@isardsat/editorial-common";
2
+ import { readFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { parse } from "yaml";
5
+ import { writeFileSafe } from "./utils/fs.js";
6
+ export function createStorage(dataDirectory) {
7
+ const schemaPath = join(dataDirectory, "schema.yaml");
8
+ const dataPath = join(dataDirectory, "data.json");
9
+ const dataProdPath = join(dataDirectory, "data.prod.json");
10
+ const dataExtractedPath = join(dataDirectory, "data.messages.json");
11
+ async function getSchema() {
12
+ const schemaFile = await readFile(schemaPath, "utf-8").then((value) => parse(value));
13
+ const schema = EditorialSchemaSchema.parse(schemaFile);
14
+ return schema;
15
+ }
16
+ /**
17
+ * TODO: This should ideally cache the result of reading the file until an update occurs.
18
+ */
19
+ async function getContent() {
20
+ return await readFile(dataPath, "utf-8").then((value) => JSON.parse(value));
21
+ }
22
+ async function saveContent({ production }) {
23
+ const content = await getContent();
24
+ await writeFileSafe(production ? dataProdPath : dataPath, JSON.stringify(EditorialDataSchema.parse(content), null, 2));
25
+ return true;
26
+ }
27
+ async function saveLocalisationMessages(messages) {
28
+ await writeFileSafe(dataExtractedPath, JSON.stringify(messages, null, 2));
29
+ return true;
30
+ }
31
+ async function createItem(item) {
32
+ const content = await getContent();
33
+ content[item.type][item.id] = EditorialDataItemSchema.parse(item);
34
+ // TODO: Use superjson to safely encode different types.
35
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
36
+ return item;
37
+ }
38
+ async function updateItem(item) {
39
+ const content = await getContent();
40
+ const oldItem = content[item.type][item.id];
41
+ const newItem = EditorialDataItemSchema.parse({
42
+ ...oldItem,
43
+ ...item,
44
+ updatedAt: new Date().toISOString(),
45
+ });
46
+ content[item.type][item.id] = newItem;
47
+ // TODO: Use superjson to safely encode different types.
48
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
49
+ return newItem;
50
+ }
51
+ async function deleteItem(item) {
52
+ const content = await getContent();
53
+ delete content[item.type][item.id];
54
+ // TODO: Use superjson to safely encode different types.
55
+ await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
56
+ return content;
57
+ }
58
+ return {
59
+ getSchema,
60
+ getContent,
61
+ saveLocalisationMessages,
62
+ createItem,
63
+ updateItem,
64
+ deleteItem,
65
+ saveContent,
66
+ };
67
+ }
@@ -1,9 +1,9 @@
1
- import type { EditorialData, EditorialDataObjectWithType } from '@isardsat/editorial-common';
1
+ import type { EditorialData, EditorialDataObjectWithType } from "@isardsat/editorial-common";
2
2
  export declare function createStorage(dataDirectory: string): {
3
3
  getSchema: () => Promise<Record<string, {
4
4
  displayName: string;
5
5
  fields: Record<string, import("zod").objectOutputType<{
6
- type: import("zod").ZodEnum<["string", "boolean", "date", "datetime", "markdown", "number", "color", "select", "color"]>;
6
+ type: import("zod").ZodEnum<["string", "url", "boolean", "date", "datetime", "markdown", "number", "color", "select", "color"]>;
7
7
  displayName: import("zod").ZodString;
8
8
  displayExtra: import("zod").ZodOptional<import("zod").ZodString>;
9
9
  placeholder: import("zod").ZodOptional<import("zod").ZodString>;
@@ -13,6 +13,7 @@ export declare function createStorage(dataDirectory: string): {
13
13
  singleton?: boolean | undefined;
14
14
  }>>;
15
15
  getContent: () => Promise<EditorialData>;
16
+ getLocalisationMessages: (langCode: string) => Promise<any>;
16
17
  saveLocalisationMessages: (messages: any) => Promise<boolean>;
17
18
  createItem: (item: EditorialDataObjectWithType) => Promise<import("zod").objectOutputType<{
18
19
  id: import("zod").ZodString;
@@ -28,6 +29,8 @@ export declare function createStorage(dataDirectory: string): {
28
29
  createdAt: import("zod").ZodDefault<import("zod").ZodString>;
29
30
  updatedAt: import("zod").ZodDefault<import("zod").ZodString>;
30
31
  }, import("zod").ZodTypeAny, "passthrough">>>>;
31
- saveProdContent: () => Promise<boolean>;
32
+ saveContent: ({ production }: {
33
+ production?: boolean;
34
+ }) => Promise<boolean>;
32
35
  };
33
36
  export type Storage = ReturnType<typeof createStorage>;
@@ -1,15 +1,15 @@
1
- import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from '@isardsat/editorial-common';
2
- import { readFile } from 'fs/promises';
3
- import { join } from 'path';
4
- import { parse } from 'yaml';
5
- import { writeFileSafe } from './utils/fs.js';
1
+ import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from "@isardsat/editorial-common";
2
+ import { readFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { parse } from "yaml";
5
+ import { writeFileSafe } from "./utils/fs.js";
6
6
  export function createStorage(dataDirectory) {
7
- const schemaPath = join(dataDirectory, 'schema.yaml');
8
- const dataPath = join(dataDirectory, 'data.json');
9
- const dataProdPath = join(dataDirectory, 'data.prod.json');
10
- const dataExtractedPath = join(dataDirectory, 'data.messages.json');
7
+ const schemaPath = join(dataDirectory, "schema.yaml");
8
+ const dataPath = join(dataDirectory, "data.json");
9
+ const dataProdPath = join(dataDirectory, "data.prod.json");
10
+ const dataExtractedPath = join(dataDirectory, "data.messages.json");
11
11
  async function getSchema() {
12
- const schemaFile = await readFile(schemaPath, 'utf-8').then((value) => parse(value));
12
+ const schemaFile = await readFile(schemaPath, "utf-8").then((value) => parse(value));
13
13
  const schema = EditorialSchemaSchema.parse(schemaFile);
14
14
  return schema;
15
15
  }
@@ -17,11 +17,14 @@ export function createStorage(dataDirectory) {
17
17
  * TODO: This should ideally cache the result of reading the file until an update occurs.
18
18
  */
19
19
  async function getContent() {
20
- return await readFile(dataPath, 'utf-8').then((value) => JSON.parse(value));
20
+ return await readFile(dataPath, "utf-8").then((value) => JSON.parse(value));
21
21
  }
22
- async function saveProdContent() {
22
+ async function getLocalisationMessages(langCode) {
23
+ return await readFile(join(dataDirectory, "locales", "messages", `${langCode}.json`), "utf-8").then((value) => JSON.parse(value));
24
+ }
25
+ async function saveContent({ production }) {
23
26
  const content = await getContent();
24
- await writeFileSafe(dataProdPath, JSON.stringify(EditorialDataSchema.parse(content), null, 2));
27
+ await writeFileSafe(production ? dataProdPath : dataPath, JSON.stringify(EditorialDataSchema.parse(content), null, 2));
25
28
  return true;
26
29
  }
27
30
  async function saveLocalisationMessages(messages) {
@@ -58,10 +61,11 @@ export function createStorage(dataDirectory) {
58
61
  return {
59
62
  getSchema,
60
63
  getContent,
64
+ getLocalisationMessages,
61
65
  saveLocalisationMessages,
62
66
  createItem,
63
67
  updateItem,
64
68
  deleteItem,
65
- saveProdContent,
69
+ saveContent,
66
70
  };
67
71
  }
@@ -1,4 +1,4 @@
1
- import { OpenAPIHono } from '@hono/zod-openapi';
2
- import type { Hooks } from '../lib/hooks.js';
3
- import type { Storage } from '../lib/storage.js';
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ import type { Hooks } from "../lib/hooks.js";
3
+ import type { Storage } from "../lib/storage.js";
4
4
  export declare function createActionRoutes(storage: Storage, hooks: Hooks): OpenAPIHono<import("hono").Env, {}, "/">;
@@ -1,23 +1,35 @@
1
- import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
1
+ import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
2
+ const ActionRequestSchema = z.object({
3
+ author: z.string(),
4
+ });
2
5
  export function createActionRoutes(storage, hooks) {
3
6
  const app = new OpenAPIHono();
4
7
  app.openapi(createRoute({
5
- method: 'post',
6
- path: '/publish',
8
+ method: "post",
9
+ path: "/publish",
10
+ request: {
11
+ body: {
12
+ content: {
13
+ "application/json": {
14
+ schema: ActionRequestSchema,
15
+ },
16
+ },
17
+ },
18
+ },
7
19
  responses: {
8
20
  202: {
9
21
  content: {
10
- 'application/json': {
22
+ "application/json": {
11
23
  schema: z.boolean(),
12
24
  },
13
25
  },
14
- description: 'Trigger the publishing process',
26
+ description: "Trigger the publishing process",
15
27
  },
16
28
  },
17
29
  }),
18
30
  // TODO: Don't async, let the promises run in the background.
19
31
  async (c) => {
20
- await storage.saveProdContent();
32
+ await storage.saveContent({ production: false });
21
33
  const content = await storage.getContent();
22
34
  const schema = await storage.getSchema();
23
35
  try {
@@ -25,33 +37,53 @@ export function createActionRoutes(storage, hooks) {
25
37
  await storage.saveLocalisationMessages(scriptResult);
26
38
  }
27
39
  catch (error) {
28
- console.error('Error executing script:', error);
40
+ console.error("Error executing script:", error);
29
41
  return c.json(false);
30
42
  }
31
43
  return c.json(true);
32
44
  });
33
45
  app.openapi(createRoute({
34
- method: 'post',
35
- path: '/pull',
46
+ method: "post",
47
+ path: "/pull",
48
+ request: {
49
+ body: {
50
+ content: {
51
+ "application/json": {
52
+ schema: ActionRequestSchema,
53
+ },
54
+ },
55
+ },
56
+ },
36
57
  responses: {
37
58
  200: {
38
- description: 'Trigger the pull process',
59
+ description: "Trigger the pull process",
39
60
  },
40
61
  },
41
62
  }), async (c) => {
42
- await hooks.onPull();
63
+ const { author } = c.req.valid("json");
64
+ await hooks.onPull(author);
43
65
  return c.json(true);
44
66
  });
45
67
  app.openapi(createRoute({
46
- method: 'post',
47
- path: '/push',
68
+ method: "post",
69
+ path: "/push",
70
+ request: {
71
+ body: {
72
+ content: {
73
+ "application/json": {
74
+ schema: ActionRequestSchema,
75
+ },
76
+ },
77
+ },
78
+ },
48
79
  responses: {
49
80
  200: {
50
- description: 'Trigger the push process',
81
+ description: "Trigger the push process",
51
82
  },
52
83
  },
53
84
  }), async (c) => {
54
- await hooks.onPush();
85
+ const { author } = c.req.valid("json");
86
+ await hooks.onPush(author);
55
87
  return c.json(true);
56
88
  });
57
89
  return app;
@@ -0,0 +1,3 @@
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ import type { EditorialConfig } from "@isardsat/editorial-common";
3
+ export declare function createConfigRoutes(config: EditorialConfig): OpenAPIHono<import("hono").Env, {}, "/">;
@@ -0,0 +1,23 @@
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ import { EditorialConfigSchema } from "@isardsat/editorial-common";
3
+ export function createConfigRoutes(config) {
4
+ const app = new OpenAPIHono();
5
+ app.openapi({
6
+ method: "get",
7
+ path: "/config",
8
+ summary: "Get Editorial configuration",
9
+ responses: {
10
+ 200: {
11
+ content: {
12
+ "application/json": {
13
+ schema: EditorialConfigSchema,
14
+ },
15
+ },
16
+ description: "Editorial configuration",
17
+ },
18
+ },
19
+ }, (c) => {
20
+ return c.json(config);
21
+ });
22
+ return app;
23
+ }
@@ -45,12 +45,14 @@ export function createDataRoutes(storage) {
45
45
  param: { name: "itemType", in: "path" },
46
46
  example: "newsItem",
47
47
  }),
48
- locale: z
48
+ }),
49
+ query: z.object({
50
+ lang: z
49
51
  .string()
50
52
  .optional()
51
53
  .openapi({
52
- param: { name: "locale", in: "query" },
53
- example: "es_ES",
54
+ param: { name: "lang", in: "query" },
55
+ example: "es",
54
56
  }),
55
57
  }),
56
58
  },
@@ -66,8 +68,20 @@ export function createDataRoutes(storage) {
66
68
  },
67
69
  }), async (c) => {
68
70
  const { itemType } = c.req.valid("param");
71
+ const { lang } = c.req.valid("query");
69
72
  const content = await storage.getContent();
70
- return c.json(content[itemType]);
73
+ const collection = content[itemType];
74
+ // TODO: Formalize this process.
75
+ if (lang) {
76
+ const messages = await storage.getLocalisationMessages(lang);
77
+ for (const [key, message] of Object.entries(messages)) {
78
+ const [contentKey, typeKey, fieldKey, hash] = key.split(".");
79
+ if (contentKey === itemType) {
80
+ collection[typeKey][fieldKey] = message.defaultMessage;
81
+ }
82
+ }
83
+ }
84
+ return c.json(collection);
71
85
  });
72
86
  app.openapi(createRoute({
73
87
  method: "get",
@@ -108,12 +122,14 @@ export function createDataRoutes(storage) {
108
122
  param: { name: "id", in: "path" },
109
123
  example: "about-us",
110
124
  }),
111
- locale: z
125
+ }),
126
+ query: z.object({
127
+ lang: z
112
128
  .string()
113
129
  .optional()
114
130
  .openapi({
115
- param: { name: "locale", in: "query" },
116
- example: "es_ES",
131
+ param: { name: "lang", in: "query" },
132
+ example: "es",
117
133
  }),
118
134
  }),
119
135
  },
@@ -129,8 +145,20 @@ export function createDataRoutes(storage) {
129
145
  },
130
146
  }), async (c) => {
131
147
  const { itemType, id } = c.req.valid("param");
148
+ const { lang } = c.req.valid("query");
132
149
  const content = await storage.getContent();
133
- return c.json(content[itemType][id]);
150
+ const item = content[itemType][id];
151
+ // TODO: Formalize this process.
152
+ if (lang) {
153
+ const messages = await storage.getLocalisationMessages(lang);
154
+ for (const [key, message] of Object.entries(messages)) {
155
+ const [contentKey, typeKey, fieldKey, hash] = key.split(".");
156
+ if (typeKey === id) {
157
+ item[fieldKey] = message.defaultMessage;
158
+ }
159
+ }
160
+ }
161
+ return c.json(item);
134
162
  });
135
163
  app.openapi(createRoute({
136
164
  method: "put",
@@ -1,2 +1,2 @@
1
- import { OpenAPIHono } from '@hono/zod-openapi';
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  export declare function createFilesRoutes(): OpenAPIHono<import("hono").Env, {}, "/">;
@@ -1,30 +1,41 @@
1
- import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
2
- import { EditorialFilesSchema, } from '@isardsat/editorial-common';
3
- import { readdirSync, statSync } from 'node:fs';
4
- import { access, constants, mkdir, rename } from 'node:fs/promises';
5
- import { basename, join, normalize } from 'node:path';
1
+ import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
2
+ import { EditorialFilesResponseSchema, } from "@isardsat/editorial-common";
3
+ import { readdirSync, statSync } from "node:fs";
4
+ import { access, constants, mkdir, rename } from "node:fs/promises";
5
+ import { basename, join, normalize } from "node:path";
6
6
  export function createFilesRoutes() {
7
7
  const app = new OpenAPIHono();
8
- const publicDirPath = 'public';
9
- const deletedDirPath = 'public/.deleted';
8
+ const publicDirPath = "public";
9
+ const deletedDirPath = "public/.deleted";
10
10
  app.openapi(createRoute({
11
- method: 'get',
12
- path: '/files',
11
+ method: "get",
12
+ path: "/files",
13
13
  responses: {
14
14
  200: {
15
15
  content: {
16
- 'application/json': {
17
- schema: EditorialFilesSchema,
16
+ "application/json": {
17
+ schema: EditorialFilesResponseSchema,
18
18
  },
19
19
  },
20
- description: 'Get tree of public files',
20
+ description: "Get tree of public files with total size",
21
21
  },
22
22
  },
23
23
  }), async (c) => {
24
+ function calculateTotalSize(files) {
25
+ return files.reduce((total, file) => {
26
+ if (file.type === "file") {
27
+ return total + file.size;
28
+ }
29
+ else if (file.children) {
30
+ return total + calculateTotalSize(file.children);
31
+ }
32
+ return total;
33
+ }, 0);
34
+ }
24
35
  function readDirectoryChildren(path) {
25
36
  const directory = readdirSync(path);
26
37
  return directory
27
- .filter((fileName) => !fileName.startsWith('.'))
38
+ .filter((fileName) => !fileName.startsWith("."))
28
39
  .map((fileName) => {
29
40
  const file = statSync(join(path, fileName));
30
41
  const isDirectory = file.isDirectory();
@@ -32,24 +43,25 @@ export function createFilesRoutes() {
32
43
  name: basename(fileName),
33
44
  path: join(path, fileName),
34
45
  size: file.size,
35
- type: isDirectory ? 'directory' : 'file',
46
+ type: isDirectory ? "directory" : "file",
36
47
  children: isDirectory
37
48
  ? readDirectoryChildren(join(path, fileName))
38
49
  : undefined,
39
50
  };
40
51
  })
41
- .sort((a, b) => (a.type === 'directory' ? -1 : 0));
52
+ .sort((a, b) => (a.type === "directory" ? -1 : 0));
42
53
  }
43
54
  const files = readDirectoryChildren(publicDirPath);
44
- return c.json(files);
55
+ const totalSize = calculateTotalSize(files);
56
+ return c.json({ files, totalSize });
45
57
  });
46
58
  app.openapi(createRoute({
47
- method: 'delete',
48
- path: '/files',
59
+ method: "delete",
60
+ path: "/files",
49
61
  request: {
50
62
  body: {
51
63
  content: {
52
- 'application/json': {
64
+ "application/json": {
53
65
  schema: z.object({
54
66
  path: z.string(),
55
67
  }),
@@ -61,55 +73,55 @@ export function createFilesRoutes() {
61
73
  responses: {
62
74
  200: {
63
75
  content: {
64
- 'application/json': {
76
+ "application/json": {
65
77
  schema: z.boolean(),
66
78
  },
67
79
  },
68
- description: '',
80
+ description: "",
69
81
  },
70
82
  400: {
71
83
  content: {
72
- 'application/json': {
84
+ "application/json": {
73
85
  schema: z.object({
74
86
  error: z.string(),
75
87
  }),
76
88
  },
77
89
  },
78
- description: 'Invalid file path',
90
+ description: "Invalid file path",
79
91
  },
80
92
  404: {
81
93
  content: {
82
- 'application/json': {
94
+ "application/json": {
83
95
  schema: z.object({
84
96
  error: z.string(),
85
97
  }),
86
98
  },
87
99
  },
88
- description: 'File not found',
100
+ description: "File not found",
89
101
  },
90
102
  500: {
91
103
  content: {
92
- 'application/json': {
104
+ "application/json": {
93
105
  schema: z.object({
94
106
  error: z.string(),
95
107
  }),
96
108
  },
97
109
  },
98
- description: 'Server error',
110
+ description: "Server error",
99
111
  },
100
112
  },
101
113
  }), async (c) => {
102
- const { path: filePath } = c.req.valid('json');
114
+ const { path: filePath } = c.req.valid("json");
103
115
  try {
104
116
  const normalizedPath = normalize(filePath);
105
117
  if (!normalizedPath.startsWith(publicDirPath)) {
106
- return c.json({ error: 'Invalid file path' }, 400);
118
+ return c.json({ error: "Invalid file path" }, 400);
107
119
  }
108
120
  const exists = await access(filePath, constants.W_OK)
109
121
  .then(() => true)
110
122
  .catch(() => false);
111
123
  if (!exists) {
112
- return c.json({ error: 'File not found' }, 404);
124
+ return c.json({ error: "File not found" }, 404);
113
125
  }
114
126
  // Move to deleted directory
115
127
  await mkdir(deletedDirPath, { recursive: true });
@@ -117,8 +129,8 @@ export function createFilesRoutes() {
117
129
  return c.json(true, 200);
118
130
  }
119
131
  catch (err) {
120
- console.error('Delete failed:', err);
121
- return c.json({ error: 'Server error' }, 500);
132
+ console.error("Delete failed:", err);
133
+ return c.json({ error: "Server error" }, 500);
122
134
  }
123
135
  });
124
136
  return app;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isardsat/editorial-server",
3
- "version": "6.0.2",
3
+ "version": "6.0.4",
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.6.20",
15
15
  "yaml": "^2.7.0",
16
16
  "zod": "^3.24.1",
17
- "@isardsat/editorial-admin": "^6.0.2",
18
- "@isardsat/editorial-common": "^6.0.2"
17
+ "@isardsat/editorial-admin": "^6.0.4",
18
+ "@isardsat/editorial-common": "^6.0.4"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@tsconfig/node22": "^22.0.0",