diff --git a/backend/package-lock.json b/backend/package-lock.json index 3967c09..560722a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@bytescale/sdk": "^3.53.0", "@editorjs/editorjs": "^2.31.4", "@editorjs/header": "^2.8.8", "@editorjs/list": "^2.0.9", @@ -21,6 +22,7 @@ "express-session": "^1.19.0", "jsonwebtoken": "^9.0.3", "multer": "^2.1.1", + "node-fetch": "^3.3.2", "postmark": "^4.0.7", "prisma": "^6.19.2", "slugify": "^1.6.9" @@ -29,6 +31,12 @@ "nodemon": "^3.1.11" } }, + "node_modules/@bytescale/sdk": { + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@bytescale/sdk/-/sdk-3.53.0.tgz", + "integrity": "sha512-qCeNup3pSjaklXuBrO9JeKbozZEs/PjQEvuqCiOAWLBRl6lDjd0V9gRVYqyttPimXYFoV+J/7dmPWtK6RfOABQ==", + "license": "MIT" + }, "node_modules/@codexteam/icons": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.5.tgz", @@ -600,6 +608,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -900,6 +917,29 @@ "node": ">=8.0.0" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -991,6 +1031,18 @@ "node": ">= 0.6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1522,6 +1574,44 @@ "node": "^18 || ^20 || >= 21" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-fetch-native": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", @@ -2216,6 +2306,15 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/backend/package.json b/backend/package.json index b18dc4c..d208b7c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,6 +15,7 @@ "license": "ISC", "type": "module", "dependencies": { + "@bytescale/sdk": "^3.53.0", "@editorjs/editorjs": "^2.31.4", "@editorjs/header": "^2.8.8", "@editorjs/list": "^2.0.9", @@ -27,6 +28,7 @@ "express-session": "^1.19.0", "jsonwebtoken": "^9.0.3", "multer": "^2.1.1", + "node-fetch": "^3.3.2", "postmark": "^4.0.7", "prisma": "^6.19.2", "slugify": "^1.6.9" diff --git a/backend/src/routes/upload.routes.js b/backend/src/routes/upload.routes.js index c6a4556..9197686 100644 --- a/backend/src/routes/upload.routes.js +++ b/backend/src/routes/upload.routes.js @@ -1,15 +1,35 @@ import express from "express"; -import {upload} from "../controllers/upload.controller.js"; +import * as Bytescale from "@bytescale/sdk"; +import multer from "multer"; const router = express.Router(); -router.post("/image", upload.single("image"), (req, res) => { - res.json({ - success: 1, - file: { - url: `http://localhost:3000/uploads/blog/${req.file.filename}`, - }, - }); +const uploadManager = new Bytescale.UploadManager({ + apiKey: process.env.BYTESCALE_SECRET_API_KEY, +}); + +const storage = multer.memoryStorage(); +const upload = multer({storage}); + +router.post("/", upload.single("file"), async (req, res) => { + try { + const file = req.file; + const {folderPath} = req.body; + + const result = await uploadManager.upload({ + data: file.buffer, + name: file.originalname, + mime: file.mimetype, + path: { + folderPath: folderPath || "/general", + }, + }); + + res.json({fileUrl: result.fileUrl}); + } catch (error) { + console.error(error); + res.status(500).json({error: "Upload failed"}); + } }); export default router; diff --git a/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx b/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx index 7636aa7..792612f 100644 --- a/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx +++ b/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx @@ -1,7 +1,7 @@ -import { useState, useRef } from "react"; -import * as Bytescale from "@bytescale/sdk"; -import { Button } from "@/components/ui/button"; -import { User, X, Loader2 } from "lucide-react"; +import {useState, useRef} from "react"; +import {Button} from "@/components/ui/button"; +import {User, X, Loader2} from "lucide-react"; +import axios from "axios"; interface BytescaleUploaderProps { value: string; @@ -9,11 +9,11 @@ interface BytescaleUploaderProps { folderPath: "/doctors" | "/departments" | "/news"; } -const uploadManager = new Bytescale.UploadManager({ - apiKey: "public_223k2cv3MyaAxBeyALCfJa8EMLek", -}); - -export function BytescaleUploader({ value, onChange }: BytescaleUploaderProps) { +export function BytescaleUploader({ + value, + onChange, + folderPath, +}: BytescaleUploaderProps) { const [isUploading, setIsUploading] = useState(false); const fileInputRef = useRef(null); @@ -21,19 +21,35 @@ export function BytescaleUploader({ value, onChange }: BytescaleUploaderProps) { const file = event.target.files?.[0]; if (!file) return; - setIsUploading(true); - try { - const { fileUrl } = await uploadManager.upload({ - data: file, - path: { - folderPath: "/doctors", - }, - }); + if (file.size > 5 * 1024 * 1024) { + alert("File is too large (Max 5MB)"); + return; + } + setIsUploading(true); + + const formData = new FormData(); + formData.append("file", file); + formData.append("folderPath", folderPath); + + try { + const response = await axios.post( + "http://localhost:3000/api/upload", + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + }, + ); + + const {fileUrl} = response.data; onChange(fileUrl); } catch (e: any) { console.error("Upload Error:", e); - alert(`Error:\n${e.message}`); + const errorMessage = + e.response?.data?.error || e.message || "Upload failed"; + alert(`Upload Error: ${errorMessage}`); } finally { setIsUploading(false); if (fileInputRef.current) fileInputRef.current.value = ""; @@ -49,7 +65,7 @@ export function BytescaleUploader({ value, onChange }: BytescaleUploaderProps) { Doctor Profile