@isardsat/editorial-server 6.13.0 → 6.13.2

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.
Files changed (2) hide show
  1. package/dist/routes/files.js +109 -30
  2. package/package.json +3 -3
@@ -2,7 +2,7 @@ import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
2
2
  import { EditorialFilesResponseSchema, } from "@isardsat/editorial-common";
3
3
  import { readdirSync, statSync } from "node:fs";
4
4
  import { access, constants, mkdir, rename, writeFile } from "node:fs/promises";
5
- import { basename, join, normalize, relative } from "node:path";
5
+ import { basename, dirname, join, normalize, relative } from "node:path";
6
6
  export async function createFilesRoutes(config) {
7
7
  const app = new OpenAPIHono();
8
8
  // Dynamically import the ES module and call its init function
@@ -114,6 +114,7 @@ export async function createFilesRoutes(config) {
114
114
  content: {
115
115
  "application/json": {
116
116
  schema: z.object({
117
+ // relative to publicDirPath, e.g. "images/2026/a.png" or "images/2026"
117
118
  path: z.string(),
118
119
  }),
119
120
  },
@@ -123,60 +124,46 @@ export async function createFilesRoutes(config) {
123
124
  },
124
125
  responses: {
125
126
  200: {
126
- content: {
127
- "application/json": {
128
- schema: z.boolean(),
129
- },
130
- },
131
- description: "",
127
+ content: { "application/json": { schema: z.boolean() } },
128
+ description: "Deleted (moved to deleted folder)",
132
129
  },
133
130
  400: {
134
131
  content: {
135
- "application/json": {
136
- schema: z.object({
137
- error: z.string(),
138
- }),
139
- },
132
+ "application/json": { schema: z.object({ error: z.string() }) },
140
133
  },
141
134
  description: "Invalid file path",
142
135
  },
143
136
  404: {
144
137
  content: {
145
- "application/json": {
146
- schema: z.object({
147
- error: z.string(),
148
- }),
149
- },
138
+ "application/json": { schema: z.object({ error: z.string() }) },
150
139
  },
151
140
  description: "File not found",
152
141
  },
153
142
  500: {
154
143
  content: {
155
- "application/json": {
156
- schema: z.object({
157
- error: z.string(),
158
- }),
159
- },
144
+ "application/json": { schema: z.object({ error: z.string() }) },
160
145
  },
161
146
  description: "Server error",
162
147
  },
163
148
  },
164
149
  }), async (c) => {
165
- const { path: filePath } = c.req.valid("json");
150
+ const { path: relativePathInput } = c.req.valid("json");
166
151
  try {
167
- const normalizedPath = normalize(filePath);
168
- if (!normalizedPath.startsWith(publicDirPath)) {
152
+ const rel = normalize(relativePathInput).replace(/^([/\\])+/, "");
153
+ const absoluteSource = normalize(join(publicDirPath, rel));
154
+ const publicRoot = normalize(publicDirPath + (publicDirPath.endsWith("/") ? "" : "/"));
155
+ if (!absoluteSource.startsWith(publicRoot)) {
169
156
  return c.json({ error: "Invalid file path" }, 400);
170
157
  }
171
- const exists = await access(filePath, constants.W_OK)
158
+ const exists = await access(absoluteSource, constants.F_OK)
172
159
  .then(() => true)
173
160
  .catch(() => false);
174
161
  if (!exists) {
175
162
  return c.json({ error: "File not found" }, 404);
176
163
  }
177
- // Move to deleted directory
178
- await mkdir(deletedDirPath, { recursive: true });
179
- await rename(filePath, join(deletedDirPath, basename(filePath)));
164
+ const absoluteTarget = normalize(join(deletedDirPath, rel));
165
+ await mkdir(dirname(absoluteTarget), { recursive: true });
166
+ await rename(absoluteSource, absoluteTarget);
180
167
  return c.json(true, 200);
181
168
  }
182
169
  catch (err) {
@@ -249,7 +236,9 @@ export async function createFilesRoutes(config) {
249
236
  for (const file of fileArray) {
250
237
  if (file instanceof File) {
251
238
  if (file.size >= 1e6) {
252
- return c.json({ error: "File too large" }, 400);
239
+ await largeFilesHandler.upload(file, { path: targetPath });
240
+ uploadedFiles.push(file.name);
241
+ continue;
253
242
  }
254
243
  const fileName = file.name;
255
244
  const filePath = join(normalizedTargetDir, fileName);
@@ -265,5 +254,95 @@ export async function createFilesRoutes(config) {
265
254
  return c.json({ error: "Server error" }, 500);
266
255
  }
267
256
  });
257
+ app.openapi(createRoute({
258
+ method: "post",
259
+ path: "/files/directory",
260
+ request: {
261
+ body: {
262
+ content: {
263
+ "application/json": {
264
+ schema: z.object({
265
+ // path relative to publicDirPath, e.g. "images/2026"
266
+ path: z.string().optional().default(""),
267
+ // directory name to create, e.g. "new-folder"
268
+ name: z.string().min(1),
269
+ }),
270
+ },
271
+ },
272
+ required: true,
273
+ },
274
+ },
275
+ responses: {
276
+ 201: {
277
+ content: {
278
+ "application/json": {
279
+ schema: z.object({
280
+ relativePath: z.string(),
281
+ }),
282
+ },
283
+ },
284
+ description: "Directory created",
285
+ },
286
+ 400: {
287
+ content: {
288
+ "application/json": {
289
+ schema: z.object({
290
+ error: z.string(),
291
+ }),
292
+ },
293
+ },
294
+ description: "Invalid directory path/name",
295
+ },
296
+ 409: {
297
+ content: {
298
+ "application/json": {
299
+ schema: z.object({
300
+ error: z.string(),
301
+ }),
302
+ },
303
+ },
304
+ description: "Directory already exists",
305
+ },
306
+ 500: {
307
+ content: {
308
+ "application/json": {
309
+ schema: z.object({
310
+ error: z.string(),
311
+ }),
312
+ },
313
+ },
314
+ description: "Server error",
315
+ },
316
+ },
317
+ }), async (c) => {
318
+ try {
319
+ const { path, name } = c.req.valid("json");
320
+ // Avoid path traversal and invalid names
321
+ if (name.includes("/") ||
322
+ name.includes("\\") ||
323
+ name === "." ||
324
+ name === "..") {
325
+ return c.json({ error: "Invalid directory name" }, 400);
326
+ }
327
+ const targetDir = join(publicDirPath, path ?? "", name);
328
+ const normalizedTargetDir = normalize(targetDir);
329
+ if (!normalizedTargetDir.startsWith(publicDirPath)) {
330
+ return c.json({ error: "Invalid directory path" }, 400);
331
+ }
332
+ const exists = await access(normalizedTargetDir, constants.F_OK)
333
+ .then(() => true)
334
+ .catch(() => false);
335
+ if (exists) {
336
+ return c.json({ error: "Directory already exists" }, 409);
337
+ }
338
+ await mkdir(normalizedTargetDir, { recursive: true });
339
+ const relativePath = relative(publicDirPath, normalizedTargetDir);
340
+ return c.json({ relativePath }, 201);
341
+ }
342
+ catch (err) {
343
+ console.error("Create directory failed:", err);
344
+ return c.json({ error: "Server error" }, 500);
345
+ }
346
+ });
268
347
  return app;
269
348
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isardsat/editorial-server",
3
- "version": "6.13.0",
3
+ "version": "6.13.2",
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.0",
18
- "@isardsat/editorial-common": "^6.13.0"
17
+ "@isardsat/editorial-admin": "^6.13.2",
18
+ "@isardsat/editorial-common": "^6.13.2"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@tsconfig/node22": "^22.0.0",