Skip to content

Commit b602849

Browse files
committed
feat: update image optimization endpoint and enhance database functionality
1 parent bfdf6ca commit b602849

File tree

6 files changed

+437
-24
lines changed

6 files changed

+437
-24
lines changed

docs/collections/collection.postman_collection.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@
413413
"method": "GET",
414414
"header": [],
415415
"url": {
416-
"raw": "{{url}}/source/v1/files/image/:filename?fm=webp&q=80&w=500&h=500&fit=cover",
416+
"raw": "{{url}}/assets/image/:filename?fm=webp&q=80&w=500&h=500&fit=cover",
417417
"host": [
418418
"{{url}}"
419419
],
@@ -454,7 +454,7 @@
454454
}
455455
]
456456
},
457-
"description": "## Image optimization endpoint\n\n**Endpoint:** \n`GET /source/v1/files/image/:filename`\n\n**Description:** \nThis endpoint allows you to retrieve and optimize an image file stored on the server by specifying the filename as a URL parameter. You can apply various optimization options through query parameters.\n\n### URL Parameters\n\n| Key | Type | Description |\n| --- | --- | --- |\n| filename | String | The name of the image file to retrieve and optimize |\n\n### Query Parameters\n\n| Key | Type | Description |\n| --- | --- | --- |\n| fm | String | (Optional) Format (e.g., jpg, png, webp) |\n| q | Number | (Optional) Quality (e.g., 80) |\n| w | Number | (Optional) Width (e.g., 300) |\n| h | Number | (Optional) Height (e.g., 300) |\n| fit | String | (Optional) Fit mode (e.g., cover, contain) |\n\n### Example Request in Postman\n\n1. Set the request method to **GET**.\n \n2. Set the URL to: `{{url}}/source/v1/files/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover`\n \n3. (Optional) Add any required headers.\n \n4. Click **Send** to retrieve and optimize the image.\n \n\n### Example cURL\n\n``` sh\ncurl -X GET \"{{url}}/source/v1/files/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover\"\n\n ```\n\n### Example Response\n\nThe response will be the optimized image file. The content type will depend on the requested format (e.g., `image/webp` for WebP format).\n\n``` http\nHTTP/1.1 200 OK\nContent-Type: image/webp\nContent-Length: 12345\n(binary image data)\n\n ```"
457+
"description": "## Image optimization endpoint\n\n**Endpoint:** \n`GET /assets/image/:filename`\n\n**Description:** \nThis endpoint allows you to retrieve and optimize an image file stored on the server by specifying the filename as a URL parameter. You can apply various optimization options through query parameters.\n\n### URL Parameters\n\n| Key | Type | Description |\n| --- | --- | --- |\n| filename | String | The name of the image file to retrieve and optimize |\n\n### Query Parameters\n\n| Key | Type | Description |\n| --- | --- | --- |\n| fm | String | (Optional) Format (e.g., jpg, png, webp) |\n| q | Number | (Optional) Quality (e.g., 80) |\n| w | Number | (Optional) Width (e.g., 300) |\n| h | Number | (Optional) Height (e.g., 300) |\n| fit | String | (Optional) Fit mode (e.g., cover, contain) |\n\n### Example Request in Postman\n\n1. Set the request method to **GET**.\n \n2. Set the URL to: `{{url}}/assets/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover`\n \n3. (Optional) Add any required headers.\n \n4. Click **Send** to retrieve and optimize the image.\n \n\n### Example cURL\n\n``` sh\ncurl -X GET \"{{url}}/assets/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover\"\n\n ```\n\n### Example Response\n\nThe response will be the optimized image file. The content type will depend on the requested format (e.g., `image/webp` for WebP format).\n\n``` http\nHTTP/1.1 200 OK\nContent-Type: image/webp\nContent-Length: 12345\n(binary image data)\n\n ```"
458458
},
459459
"response": []
460460
},

docs/how-to-use/get-image-endpoint.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Image optimization endpoint
22
**Endpoint:**
3-
`GET /source/v1/files/image/:filename`
3+
`GET /assets/image/:filename`
44

55
**Description:**
66
This endpoint allows you to retrieve and optimize an image file stored on the server by specifying the filename as a URL parameter. You can apply various optimization options through query parameters.
@@ -21,13 +21,13 @@ This endpoint allows you to retrieve and optimize an image file stored on the se
2121

2222
### Example Request in Postman
2323
1. Set the request method to **GET**.
24-
2. Set the URL to: `{{url}}/source/v1/files/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover`
24+
2. Set the URL to: `{{url}}/assets/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover`
2525
3. (Optional) Add any required headers.
2626
4. Click **Send** to retrieve and optimize the image.
2727

2828
### Example cURL
2929
```sh
30-
curl -X GET "{{url}}/source/v1/files/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover"
30+
curl -X GET "{{url}}/assets/image/sample.png?fm=webp&q=80&w=300&h=300&fit=cover"
3131
```
3232

3333
### Example Response

src/controllers/files.controller.ts

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { configured } from '../utils/config.js';
44
import { UploadController } from './upload.controller.js';
55
import { sendBadRequest, sendNotFound, sendSuccess } from '../utils/response.js';
66
import { FileUtils } from '../utils/files.js';
7+
import { Database } from '../utils/database.js';
78
import fs from 'fs';
89

910
interface FileValidateCallback {
@@ -217,6 +218,9 @@ export class FilesController {
217218

218219
// reduce value of key "path" to be relative to storage directory
219220
for (const file of sanitizedFiles) {
221+
// Preserve the original file system path before modification
222+
const originalFilePath = file.path;
223+
220224
const sanitizedFile: any = {
221225
...file,
222226
// path: file.path.replace(/\\/g, '/'),
@@ -229,8 +233,8 @@ export class FilesController {
229233
};
230234
Object.assign(file, sanitizedFile);
231235

232-
// Sync file after upload
233-
await FilesController.syncFile(file.path);
236+
// Sync file after upload with original filename - use the original filesystem path
237+
await FilesController.syncFile(originalFilePath, file.originalname);
234238
}
235239

236240
sendSuccess(response, sanitizedFiles, 'Files uploaded successfully', 200);
@@ -240,8 +244,9 @@ export class FilesController {
240244
/**
241245
* Sync a file after upload
242246
* @param filePath Path to the file to sync
247+
* @param originalFilename Original filename from upload (optional)
243248
*/
244-
static async syncFile(filePath: string): Promise<void> {
249+
static async syncFile(filePath: string, originalFilename?: string): Promise<void> {
245250
try {
246251
// Get the actual file path on disk
247252
const actualFilePath = filePath.startsWith(configured.baseDirectory) ? filePath : `${configured.baseDirectory}${filePath}`;
@@ -262,23 +267,73 @@ export class FilesController {
262267
// Ensure proper permissions are set
263268
await FileUtils.setProperPermissions(actualFilePath, false);
264269

265-
// Log sync information
266-
console.log(`File synced successfully:`, {
267-
path: actualFilePath,
268-
folder: folderPath,
270+
// Extract filename
271+
const filename = actualFilePath.replace(/\\/g, '/').split('/').pop() || 'unknown';
272+
const ext = filename.split('.').pop()?.toLowerCase();
273+
274+
// Check if file is an image
275+
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'];
276+
const isImage = ext ? imageExtensions.includes(ext) : false;
277+
278+
// Create file metadata
279+
const metadata = {
280+
filename: filename,
281+
originalFilename: originalFilename || filename,
282+
path: actualFilePath.replace(/\\/g, '/'),
283+
relativePath: relativePath,
284+
folderPath: folderPath,
269285
size: stats.size,
270-
syncedAt: new Date().toISOString()
271-
});
286+
extension: ext || '',
287+
mimeType: this.getMimeType(ext || ''),
288+
previewUrl: isImage ? `/assets/image/${filename}` : `/file/preview/${filename}`,
289+
createdAt: stats.birthtime.toISOString(),
290+
modifiedAt: stats.mtime.toISOString(),
291+
uploadedAt: new Date().toISOString()
292+
};
272293

273-
// Optional: Create a sync record or backup based on folder
274-
// This could be extended to copy files to a backup location
275-
// based on the folder structure
294+
// Save to database
295+
await Database.addFile(metadata);
276296

277297
} catch (error: any) {
278298
console.error(`Error syncing file ${filePath}:`, error.message);
279299
}
280300
}
281301

302+
303+
304+
/**
305+
* Get MIME type based on file extension
306+
* @param ext File extension
307+
*/
308+
static getMimeType(ext: string): string {
309+
const mimeTypes: { [key: string]: string } = {
310+
// Images
311+
'jpg': 'image/jpeg',
312+
'jpeg': 'image/jpeg',
313+
'png': 'image/png',
314+
'gif': 'image/gif',
315+
'webp': 'image/webp',
316+
'svg': 'image/svg+xml',
317+
// Documents
318+
'pdf': 'application/pdf',
319+
'doc': 'application/msword',
320+
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
321+
'txt': 'text/plain',
322+
// Spreadsheets
323+
'xls': 'application/vnd.ms-excel',
324+
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
325+
'csv': 'text/csv',
326+
// Presentations
327+
'ppt': 'application/vnd.ms-powerpoint',
328+
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
329+
// Archives
330+
'zip': 'application/zip',
331+
'rar': 'application/x-rar-compressed',
332+
'7z': 'application/x-7z-compressed'
333+
};
334+
return mimeTypes[ext] || 'application/octet-stream';
335+
}
336+
282337
/**
283338
* Validate uploaded file type
284339
* @param request Request
@@ -337,7 +392,7 @@ export class FilesController {
337392
createdAt: stats.birthtime,
338393
modifiedAt: stats.mtime,
339394
relativePath: filePath,
340-
previewUrl: type === 'image' ? `/source/v1/files/image/${file}` : null
395+
previewUrl: type === 'image' ? `/assets/image/${file}` : null
341396
});
342397
});
343398
}
@@ -370,12 +425,59 @@ export class FilesController {
370425
sendNotFound(response, 'File not found.');
371426
};
372427

428+
/**
429+
* Delete a file by filename
430+
* @param request Request
431+
* @param response Response
432+
*/
433+
static deleteFile = async (request: Request, response: Response): Promise<void> => {
434+
const { filename } = request.params;
435+
436+
if (!filename) {
437+
sendBadRequest(response, 'filename is required.');
438+
return;
439+
}
440+
441+
// Find the file in configured directories
442+
const storage = configured.directories;
443+
let foundFilePath = null;
444+
445+
for (const dir of storage) {
446+
const filePath = `${dir}/${filename}`.replace(/\\/g, '/');
447+
if (fs.existsSync(filePath)) {
448+
foundFilePath = filePath;
449+
break;
450+
}
451+
}
452+
453+
if (!foundFilePath) {
454+
sendNotFound(response, 'File not found.');
455+
return;
456+
}
457+
458+
try {
459+
// Delete the file
460+
fs.unlinkSync(foundFilePath);
461+
462+
// Delete from database
463+
await Database.deleteFile(filename);
464+
465+
sendSuccess(response, {
466+
filename: filename,
467+
deletedPath: foundFilePath
468+
}, 'File deleted successfully', 200);
469+
} catch (err: any) {
470+
sendBadRequest(response, err.message || 'Failed to delete file.');
471+
}
472+
};
473+
373474
}
374475

375476
export const {
376477
uploadFiles,
377478
searchFileByName,
378479
moveFileToDir,
379480
uploadFileBase64,
380-
uploadMultipleFilesBase64
481+
uploadMultipleFilesBase64,
482+
deleteFile
381483
} = FilesController;

src/controllers/folder.controller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export class FolderController {
1111
*/
1212
static getFolderStructure = async (request: Request, response: Response): Promise<void> => {
1313
const basePath = 'storage';
14-
const dynamicPath = String(request.params.path || '');
14+
// Get the path from params[0] for regex routes, or params.path for named routes
15+
const dynamicPath = request.params[0] || request.params.path || '';
1516
const currentDirectory = path.join(basePath, dynamicPath).replace(/\\/g, '/').replace(/,+/g, '/');
1617

1718
try {

src/controllers/images.controller.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,19 +173,22 @@ export class ImagesController {
173173

174174
// reduce value of key "path" to be relative to storage directory
175175
for (const file of sanitizedFiles) {
176+
// Preserve the original file system path before modification
177+
const originalFilePath = file.path;
178+
176179
const sanitizedFile: any = {
177180
...file,
178181
fileName: file.originalname,
179-
path: `/source/v1/files/image/${file.filename}`,
180-
pathFile: `/source/v1/files/image/${file.filename}`,
182+
path: `/assets/image/${file.filename}`,
183+
pathFile: `/assets/image/${file.filename}`,
181184
type: file.mimetype,
182185
name: file.filename,
183186
extension: file.originalname.split('.').pop()
184187
};
185188
Object.assign(file, sanitizedFile);
186189

187-
// Sync file after upload
188-
await FilesController.syncFile(file.path);
190+
// Sync file after upload - use the original file system path and original filename
191+
await FilesController.syncFile(originalFilePath, file.originalname);
189192
}
190193

191194
sendSuccess(response, sanitizedFiles, 'Files uploaded successfully', 200);

0 commit comments

Comments
 (0)