diff --git a/backend/.gitignore b/backend/.gitignore index 8f31ed2..efcb4ca 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ node_modules/ .env -public/uploads/ +public/uploads/* +!public/uploads/dummy.txt diff --git a/backend/migrations/20250712000000-rename-path-to-filename-in-attachments.js b/backend/migrations/20250712000000-rename-path-to-filename-in-attachments.js new file mode 100644 index 0000000..5f615b2 --- /dev/null +++ b/backend/migrations/20250712000000-rename-path-to-filename-in-attachments.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = { + up: async (queryInterface) => { + // Rename column 'path' to 'filename' in 'Attachments' table + await queryInterface.renameColumn('Attachments', 'path', 'filename'); + }, + + down: async (queryInterface) => { + // Revert column name from 'filename' back to 'path' + await queryInterface.renameColumn('Attachments', 'filename', 'path'); + }, +}; diff --git a/backend/models/attachments.js b/backend/models/attachments.js index 7be6eb5..2f47dc2 100644 --- a/backend/models/attachments.js +++ b/backend/models/attachments.js @@ -8,7 +8,7 @@ function defineAttachment(sequelize, DataTypes) { type: DataTypes.STRING, allowNull: true, }, - path: { + filename: { type: DataTypes.STRING, allowNull: false, }, diff --git a/backend/public/uploads/dummy.txt b/backend/public/uploads/dummy.txt new file mode 100644 index 0000000..e69de29 diff --git a/backend/routes/attachments/delete.js b/backend/routes/attachments/delete.js index 9bfee9b..2ded08e 100644 --- a/backend/routes/attachments/delete.js +++ b/backend/routes/attachments/delete.js @@ -1,9 +1,9 @@ +const fs = require('fs'); +const path = require('path'); const express = require('express'); const router = express.Router(); -const path = require('path'); -const fs = require('fs'); -const defineAttachment = require('../../models/attachments'); const { DataTypes } = require('sequelize'); +const defineAttachment = require('../../models/attachments'); module.exports = function (sequelize) { const Attachment = defineAttachment(sequelize, DataTypes); @@ -21,9 +21,7 @@ module.exports = function (sequelize) { // delete file from folder const uploadDir = path.join(__dirname, '../../public/uploads'); - const url = attachment.path; - const fileName = url.substring(url.lastIndexOf('/') + 1); - const filePath = path.join(uploadDir, fileName); + const filePath = path.join(uploadDir, attachment.filename); fs.unlink(filePath, (err) => { if (err) { console.error('Error deleting file:', err); diff --git a/backend/routes/attachments/download.js b/backend/routes/attachments/download.js index 35c9270..c2d8d28 100644 --- a/backend/routes/attachments/download.js +++ b/backend/routes/attachments/download.js @@ -1,9 +1,9 @@ +const fs = require('fs'); +const path = require('path'); const express = require('express'); const router = express.Router(); -const path = require('path'); -const fs = require('fs'); -const defineAttachment = require('../../models/attachments'); const { DataTypes } = require('sequelize'); +const defineAttachment = require('../../models/attachments'); module.exports = function (sequelize) { const Attachment = defineAttachment(sequelize, DataTypes); @@ -16,8 +16,7 @@ module.exports = function (sequelize) { return res.status(404).send('Attachment not found'); } - const filename = attachment.path.split('/').pop(); - const filePath = path.join(__dirname, `../../public/uploads/${filename}`); + const filePath = path.join(__dirname, `../../public/uploads/${attachment.filename}`); if (!fs.existsSync(filePath)) { return res.status(404).json({ error: 'File not found' }); diff --git a/backend/routes/attachments/new.js b/backend/routes/attachments/new.js index 2c60cc2..2771a60 100644 --- a/backend/routes/attachments/new.js +++ b/backend/routes/attachments/new.js @@ -1,11 +1,11 @@ +const fs = require('fs'); +const path = require('path'); const express = require('express'); const router = express.Router(); -const path = require('path'); -const fs = require('fs'); const multer = require('multer'); +const { DataTypes } = require('sequelize'); const defineAttachment = require('../../models/attachments'); const defineCaseAttachment = require('../../models/caseAttachments'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const Attachment = defineAttachment(sequelize, DataTypes); @@ -55,11 +55,9 @@ module.exports = function (sequelize) { return res.status(400).json({ error: 'No files uploaded' }); } - const host = req.get('host'); - const protocol = req.protocol; const attachmentsData = files.map((file) => ({ title: file.originalname, - path: `${protocol}://${host}/uploads/${file.filename}`, + filename: file.filename, })); const newAttachments = await Attachment.bulkCreate(attachmentsData, { @@ -74,6 +72,7 @@ module.exports = function (sequelize) { await t.commit(); res.json(newAttachments); } catch (error) { + console.error(error); await t.rollback(); res.status(500).json({ error: 'Internal server error' }); } diff --git a/backend/seeders/seed.js b/backend/seeders/seed.js index 6206161..83efa5c 100644 --- a/backend/seeders/seed.js +++ b/backend/seeders/seed.js @@ -550,19 +550,18 @@ module.exports = { }, ]); - const backendOrigin = process.env.BACKEND_ORIGIN || 'http://localhost:8001'; await queryInterface.bulkInsert('attachments', [ { title: 'Selenium logo', detail: '', - path: `${backendOrigin}/uploads/861px-Selenium_Logo.png`, + filename: '861px-Selenium_Logo.png', createdAt: new Date(), updatedAt: new Date(), }, { title: 'vitest logo', detail: '', - path: `${backendOrigin}/uploads/logo-shadow.svg`, + filename: 'logo-shadow.svg', createdAt: new Date(), updatedAt: new Date(), }, diff --git a/docker-compose.yaml b/docker-compose.yaml index b6e2ac5..74cfdf3 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,6 +10,7 @@ services: - IS_DEMO=false # set to true to seed the database volumes: - db-data:/app/backend/database + - ./backend/public/uploads:/app/backend/public/uploads volumes: db-data: diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx index 1357659..636e45f 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx @@ -1,8 +1,11 @@ import { Image, Button, Tooltip, Card, CardBody } from '@heroui/react'; -import { AttachmentType, CaseMessages } from '@/types/case'; import { Trash, ArrowDownToLine, ArrowUpFromLine } from 'lucide-react'; -import { isImage } from './isImage'; import { ChangeEvent, DragEvent } from 'react'; +import { isImage } from './isImage'; +import { AttachmentType, CaseMessages } from '@/types/case'; +import Config from '@/config/config'; + +const apiServer = Config.apiServer; type Props = { isDisabled: boolean; @@ -23,8 +26,8 @@ export default function CaseAttachmentsEditor({ onFilesInput, messages, }: Props) { - let images: AttachmentType[] = []; - let others: AttachmentType[] = []; + const images: AttachmentType[] = []; + const others: AttachmentType[] = []; attachments.forEach((attachment) => { if (isImage(attachment)) { @@ -39,7 +42,11 @@ export default function CaseAttachmentsEditor({ {images.map((image, index) => ( - {image.title} + {image.title}

{image.title}

diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/attachmentControl.test.ts b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/attachmentControl.test.ts index 66c08fc..ff877d0 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/attachmentControl.test.ts +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/attachmentControl.test.ts @@ -22,16 +22,16 @@ describe('attachment control', () => { id: 1, title: '', detail: '', - path: '', + filename: '', createdAt: new Date(), updatedAt: new Date(), caseAttachments: sampleCaseAttachment, }; - sampleAttachment.path = 'public/uploads/abc.png'; + sampleAttachment.filename = 'abc.png'; expect(isImage(sampleAttachment)).toBe(true); - sampleAttachment.path = 'public/uploads/abc.mp3'; + sampleAttachment.filename = 'abc.mp3'; expect(isImage(sampleAttachment)).toBe(false); }); }); diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/isImage.ts b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/isImage.ts index a0bace7..83955d2 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/isImage.ts +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/isImage.ts @@ -1,8 +1,8 @@ import { AttachmentType } from '@/types/case'; function isImage(attachmentFile: AttachmentType) { - let path = attachmentFile.path; - let extension = path.substring(path.lastIndexOf('.') + 1).toLowerCase(); + const filename = attachmentFile.filename; + const extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); if ( extension === 'png' || extension === 'jpg' || diff --git a/frontend/types/case.ts b/frontend/types/case.ts index aad9e39..6f10872 100644 --- a/frontend/types/case.ts +++ b/frontend/types/case.ts @@ -53,7 +53,7 @@ type AttachmentType = { id: number; title: string; detail: string; - path: string; + filename: string; createdAt: Date; updatedAt: Date; caseAttachments: CaseAttachmentType;