file upload and store

This commit is contained in:
Takeshi Kimata
2024-03-17 16:34:53 +09:00
parent f73060b4ad
commit aea08e9c80
3 changed files with 91 additions and 25 deletions

View File

@@ -4,10 +4,12 @@ const path = require("path");
const fs = require("fs"); const fs = require("fs");
const multer = require("multer"); const multer = require("multer");
const defineAttachment = require("../../models/attachments"); const defineAttachment = require("../../models/attachments");
const defineCaseAttachment = require("../../models/caseAttachments");
const { DataTypes } = require("sequelize"); const { DataTypes } = require("sequelize");
module.exports = function (sequelize) { module.exports = function (sequelize) {
const Attachment = defineAttachment(sequelize, DataTypes); const Attachment = defineAttachment(sequelize, DataTypes);
const CaseAttachment = defineCaseAttachment(sequelize, DataTypes);
// Create uploads folder if it does not exist // Create uploads folder if it does not exist
const uploadDir = path.join(__dirname, "../../public/uploads"); const uploadDir = path.join(__dirname, "../../public/uploads");
@@ -45,7 +47,9 @@ module.exports = function (sequelize) {
const upload = multer({ storage }); const upload = multer({ storage });
router.post("/", upload.array("files", 10), async (req, res) => { router.post("/", upload.array("files", 10), async (req, res) => {
const t = await sequelize.transaction();
try { try {
const caseId = req.query.parentCaseId;
const files = req.files; const files = req.files;
if (files.length === 0) { if (files.length === 0) {
return res.status(400).json({ error: "No files uploaded" }); return res.status(400).json({ error: "No files uploaded" });
@@ -58,10 +62,19 @@ module.exports = function (sequelize) {
path: `${protocol}://${host}/uploads/${file.filename}`, path: `${protocol}://${host}/uploads/${file.filename}`,
})); }));
const newAttachments = await Attachment.bulkCreate(attachmentsData); const newAttachments = await Attachment.bulkCreate(attachmentsData, {
transaction: t,
});
const caseAttachmentsData = newAttachments.map((attachment) => ({
caseId: caseId,
attachmentId: attachment.id,
}));
await CaseAttachment.bulkCreate(caseAttachmentsData, { transaction: t });
await t.commit();
res.json(newAttachments); res.json(newAttachments);
} catch (error) { } catch (error) {
await t.rollback();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
}); });

View File

@@ -11,18 +11,60 @@ export default function AttachmentsEditor({
attachments = [], attachments = [],
onAttachmentDelete, onAttachmentDelete,
}: Props) { }: Props) {
let images = [];
let others = [];
attachments.forEach((attachment) => {
let path = attachment.path;
let extension = path.substring(path.lastIndexOf(".") + 1).toLowerCase();
if (
extension === "png" ||
extension === "jpg" ||
extension === "jpeg" ||
extension === "gif" ||
extension === "bmp" ||
extension === "svg"
) {
images.push(attachment);
} else {
others.push(attachment);
}
});
return ( return (
<> <>
{attachments.map((attachment, index) => ( {images.map((image, index) => (
<div key={index} className="flex"> <div key={index} className="flex">
<Image alt={attachment.title} src={attachment.path} className="object-cover h-40 w-40"/> <Image
alt={image.title}
src={image.path}
className="object-cover h-40 w-40"
/>
<div className="mt-3 ms-1">
<Tooltip content="Delete this image file" placement="left">
<Button
isIconOnly
size="sm"
className="bg-transparent rounded-full"
onPress={() => onAttachmentDelete(image.id)}
>
<Trash size={16} />
</Button>
</Tooltip>
</div>
</div>
))}
{others.map((file, index) => (
<div key={index} className="flex">
<div>{file.title}</div>
<div>{file.path}</div>
<div className="mt-3 ms-1"> <div className="mt-3 ms-1">
<Tooltip content="Delete this attachment file" placement="left"> <Tooltip content="Delete this attachment file" placement="left">
<Button <Button
isIconOnly isIconOnly
size="sm" size="sm"
className="bg-transparent rounded-full" className="bg-transparent rounded-full"
onPress={() => onAttachmentDelete(attachment.id)} onPress={() => onAttachmentDelete(file.id)}
> >
<Trash size={16} /> <Trash size={16} />
</Button> </Button>

View File

@@ -245,15 +245,23 @@ export default function Page({
}); });
}; };
const handleFileUpload = async (event) => { const handleDrop = (event) => {
const files = event.target.files; event.preventDefault();
handleFileUpload(event.dataTransfer.files);
};
const handleInput = (event) => {
handleFileUpload(event.target.files);
};
const handleFileUpload = async (files) => {
try { try {
const formData = new FormData(); const formData = new FormData();
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
formData.append("files", files[i]); formData.append("files", files[i]);
} }
const url = `${apiServer}/attachments`; const url = `${apiServer}/attachments?parentCaseId=${params.caseId}`;
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",
body: formData, body: formData,
@@ -284,6 +292,21 @@ export default function Page({
return ( return (
<div className="p-5"> <div className="p-5">
<div className="mt-6">
<Button
color="primary"
isLoading={isUpdating}
onPress={async () => {
setIsUpdating(true);
await updateCase(testCase);
setTimeout(() => {
setIsUpdating(false);
}, 1000);
}}
>
{isUpdating ? "Updating..." : "Update"}
</Button>
</div>
<h6>Basic</h6> <h6>Basic</h6>
<Input <Input
size="sm" size="sm"
@@ -458,7 +481,11 @@ export default function Page({
attachments={testCase.Attachments} attachments={testCase.Attachments}
onAttachmentDelete={(id) => console.log(id)} onAttachmentDelete={(id) => console.log(id)}
/> />
<div className="flex items-center justify-center w-96 mt-3"> <div
className="flex items-center justify-center w-96 mt-3"
onDrop={handleDrop}
onDragOver={(event) => event.preventDefault()}
>
<label <label
htmlFor="dropzone-file" htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-32 border-2 border-gray-200 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600" className="flex flex-col items-center justify-center w-full h-32 border-2 border-gray-200 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
@@ -477,27 +504,11 @@ export default function Page({
id="dropzone-file" id="dropzone-file"
type="file" type="file"
className="hidden" className="hidden"
onChange={handleFileUpload} onChange={handleInput}
multiple multiple
/> />
</label> </label>
</div> </div>
<div className="mt-6">
<Button
color="primary"
isLoading={isUpdating}
onPress={async () => {
setIsUpdating(true);
await updateCase(testCase);
setTimeout(() => {
setIsUpdating(false);
}, 1000);
}}
>
{isUpdating ? "Updating..." : "Update"}
</Button>
</div>
</div> </div>
); );
} }