@anteros/core 0.0.1-alpha.8 → 0.0.2-alpha.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.
package/lib/files.ts CHANGED
@@ -6,7 +6,7 @@ import type { FileCollection } from "../types/file";
6
6
  import path from "path";
7
7
  import fs from "fs/promises";
8
8
  import { createReadStream, existsSync, mkdirSync } from "fs";
9
- import crypto from "crypto";
9
+ import { ObjectId } from "mongodb";
10
10
 
11
11
  // ─── Storage Interface ──────────────────────────────────────────────────────
12
12
 
@@ -257,38 +257,50 @@ export async function handleUpload(options: UploadOptions): Promise<FileResult>
257
257
  });
258
258
  }
259
259
 
260
- const id = crypto.randomUUID();
261
- const storage = getStorageForCollection(collection, tenant_id);
262
- const subpath = col.storage?.path || undefined;
263
- const { path: filepath, size } = await storage.save(tenant_id, collection, file, { id, mimetype, subpath });
260
+ // Insert metadata first to get the MongoDB auto-generated ObjectId
261
+ let _id: string;
262
+ try {
263
+ const rest = new useRest({ tenant_id, internal: false, useHook: false, useCustomApi: false });
264
+ const result = await rest.db.collection(collection).insertOne({
265
+ _file: {
266
+ filename: '',
267
+ name: file.name,
268
+ mimetype,
269
+ size: 0,
270
+ url: '',
271
+ },
272
+ ...(data || {}),
273
+ });
274
+ _id = String(result.insertedId);
275
+ } catch (err) {
276
+ throw new AppError('Failed to save file metadata', {
277
+ code: 'FILE_METADATA_ERROR', status: 500,
278
+ });
279
+ }
264
280
 
281
+ // Save the physical file to storage
265
282
  const ext = path.extname(file.name);
266
- const filename = `${id}${ext}`;
267
-
268
- // Enregistrer les métadonnées en base si trackMetaData est activé
269
- if (col.trackMetaData !== false) {
270
- try {
271
- const rest = new useRest({ tenant_id, internal: false, useHook: false, useCustomApi: false });
272
- await rest.insertOne(collection, {
273
- _id: id,
274
- _file: {
275
- filename,
276
- name: file.name,
277
- mimetype,
278
- size,
279
- url: `/files/${tenant_id}/${collection}/${filename}`,
280
- },
281
- ...(data || {}),
282
- });
283
- } catch (err) {
284
- await storage.delete(tenant_id, collection, id, filename).catch(() => {});
285
- throw new AppError('Failed to save file metadata', {
286
- code: 'FILE_METADATA_ERROR', status: 500,
287
- });
288
- }
283
+ const filename = `${_id}${ext}`;
284
+ const storage = getStorageForCollection(collection, tenant_id);
285
+ const subpath = col.storage?.path || undefined;
286
+ const { path: filepath, size } = await storage.save(tenant_id, collection, file, { id: _id, mimetype, subpath });
287
+
288
+ // Update MongoDB with actual file info
289
+ const url = `/files/${tenant_id}/${collection}/${filename}`;
290
+ try {
291
+ const rest = new useRest({ tenant_id, internal: true, useHook: false, useCustomApi: false });
292
+ await rest.db.collection(collection).updateOne(
293
+ { _id: new ObjectId(_id) },
294
+ { $set: { '_file.filename': filename, '_file.size': size, '_file.url': url } },
295
+ );
296
+ } catch (err) {
297
+ await storage.delete(tenant_id, collection, _id, filename, subpath).catch(() => {});
298
+ throw new AppError('Failed to update file metadata', {
299
+ code: 'FILE_METADATA_ERROR', status: 500,
300
+ });
289
301
  }
290
302
 
291
- // Réplication vers les destinations configurées
303
+ // Replicate to configured destinations
292
304
  if (col.replicate?.length) {
293
305
  replicateFile(filepath, filename, col.replicate).catch(err => {
294
306
  console.error('Replication failed:', err?.message || err);
@@ -296,13 +308,13 @@ export async function handleUpload(options: UploadOptions): Promise<FileResult>
296
308
  }
297
309
 
298
310
  return {
299
- _id: id,
311
+ _id,
300
312
  _file: {
301
313
  filename,
302
314
  name: file.name,
303
315
  mimetype,
304
316
  size,
305
- url: `/files/${tenant_id}/${collection}/${filename}`,
317
+ url,
306
318
  },
307
319
  ...(data || {}),
308
320
  };
@@ -352,12 +364,44 @@ export async function handleDelete(
352
364
  tenant_id: string,
353
365
  collection: string,
354
366
  fileId: string,
355
- filename: string,
356
367
  ): Promise<void> {
357
368
  const storage = getStorageForCollection(collection, tenant_id);
358
369
  const col = getFileCollection(collection, tenant_id);
359
370
  const subpath = col?.storage?.path || undefined;
360
- await storage.delete(tenant_id, collection, fileId, filename, subpath);
371
+
372
+ // Look up the document in MongoDB to get the stored filename
373
+ let filename: string | undefined;
374
+ try {
375
+ const rest = new useRest({ tenant_id, internal: true, useHook: false, useCustomApi: false });
376
+ const doc = await rest.db.collection(collection).findOne(
377
+ { _id: ObjectId.isValid(fileId) ? new ObjectId(fileId) : fileId },
378
+ { projection: { '_file.filename': 1 } },
379
+ );
380
+ filename = doc?._file?.filename;
381
+ } catch {}
382
+
383
+ // Delete the physical file from storage
384
+ if (filename) {
385
+ await storage.delete(tenant_id, collection, fileId, filename, subpath);
386
+ } else {
387
+ // Fallback: try common extensions using the fileId as base name
388
+ for (const ext of ['.jpg', '.jpeg', '.png', '.webp', '.avif', '.gif', '.svg', '.pdf', '.mp4', '.webm', '.mp3', '.wav', '.json', '.csv', '.zip', '']) {
389
+ try {
390
+ await storage.delete(tenant_id, collection, fileId, fileId + ext, subpath);
391
+ break;
392
+ } catch {}
393
+ }
394
+ }
395
+
396
+ // Delete the document from MongoDB
397
+ try {
398
+ const rest = new useRest({ tenant_id, internal: true, useHook: false, useCustomApi: false });
399
+ await rest.db.collection(collection).deleteOne({
400
+ _id: ObjectId.isValid(fileId) ? new ObjectId(fileId) : fileId,
401
+ });
402
+ } catch (err: any) {
403
+ console.error('Failed to delete file metadata:', err?.message || err);
404
+ }
361
405
  }
362
406
 
363
407
  // ─── Replication ──────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@anteros/core",
3
3
  "module": "index.ts",
4
- "version": "0.0.1-alpha.8",
4
+ "version": "0.0.2-alpha.0",
5
5
  "type": "module",
6
6
  "description": "Anteros Core",
7
7
  "private": false,
package/server/api.ts CHANGED
@@ -732,13 +732,12 @@ function initializeApi(app: Hono<{ Variables: HonoVariables }>) {
732
732
  }
733
733
  await checkFileAccess(colDelete?.api?.access as any, 'delete', tenant_id, c);
734
734
 
735
- const filename = basename(c.req.param('file') as string);
736
- if (!filename || filename.startsWith('.') || filename.includes('..') || filename.includes('/')) {
737
- throw new AppError('Invalid filename', { status: 400, code: 'INVALID_FILENAME' });
735
+ const fileId = c.req.param('file') as string;
736
+ if (!fileId || fileId.startsWith('.') || fileId.includes('..') || fileId.includes('/')) {
737
+ throw new AppError('Invalid file id', { status: 400, code: 'INVALID_FILE_ID' });
738
738
  }
739
- const fileId = filename.replace(/\.[^.]+$/, '');
740
739
 
741
- await handleDelete(tenant_id, collection, fileId, filename);
740
+ await handleDelete(tenant_id, collection, fileId);
742
741
 
743
742
  return c.json({ message: 'File deleted', ok: true });
744
743
  } catch (err: any) {