From c95573442ae36b7ac1833a2122c3bcff200a50b9 Mon Sep 17 00:00:00 2001 From: AngyDev Date: Sun, 22 Feb 2026 21:25:14 +0000 Subject: [PATCH 1/3] feat: add Docker support with Dockerfile, docker-compose, and environment configuration --- .env.example | 21 ++++++++++++++++ .gitignore | 3 ++- README.md | 21 +++++++++++++++- client/.dockerignore | 4 +++ client/Dockerfile | 7 ++++++ client/vite.config.js | 1 + docker-compose.yml | 57 +++++++++++++++++++++++++++++++++++++++++++ server/.dockerignore | 6 +++++ server/Dockerfile | 7 ++++++ 9 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 client/.dockerignore create mode 100644 client/Dockerfile create mode 100644 docker-compose.yml create mode 100644 server/.dockerignore create mode 100644 server/Dockerfile diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..edca4ed --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# Copy this file to .env and fill values before running docker-compose + +# Postgres DB credentials (used by the DB container) +POSTGRES_USER= +POSTGRES_PASSWORD= +POSTGRES_DB= +DB_HOST_PORT= + +# Server settings (the server container will read DB_* vars) +NODE_ENV= +NODE_PORT= +DB_HOST= +DB_PORT= +DB_NAME= +DB_USER= +DB_PASSWORD= +CLIENT_HOST= + +# Client (Vite) settings +CLIENT_PORT= +HOST= diff --git a/.gitignore b/.gitignore index d011d0e..4975d06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store .env.local -.env.development \ No newline at end of file +.env.development +.env \ No newline at end of file diff --git a/README.md b/README.md index 57f27f3..7b3ee70 100644 --- a/README.md +++ b/README.md @@ -71,4 +71,23 @@ To run client and server together use the following commands: In the custom-3D folder - Run the command `npm run install:all` -- Run the command `npm run start:all` \ No newline at end of file +- Run the command `npm run start:all` + +**Alternatively using Docker:** + +- Copy the example env and fill values (this repository gitignores `.env`): +```bash +cp .env.example .env +``` + +Then start services with docker-compose: +- Build and start all services +```bash +docker-compose up --build +``` + +- Seed/run server tasks +```bash +docker-compose exec server npm run knex:dev -- migrate:latest +docker-compose exec server npm run seed +``` \ No newline at end of file diff --git a/client/.dockerignore b/client/.dockerignore new file mode 100644 index 0000000..dc5d4c8 --- /dev/null +++ b/client/.dockerignore @@ -0,0 +1,4 @@ +node_modules +dist +.DS_Store +.env diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 0000000..f109e60 --- /dev/null +++ b/client/Dockerfile @@ -0,0 +1,7 @@ +FROM node:20 +WORKDIR /usr/src/app +COPY package*.json ./ +RUN npm install +COPY . . +EXPOSE 9000 +CMD ["npm", "run", "dev"] diff --git a/client/vite.config.js b/client/vite.config.js index 684b947..b3c58a9 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -15,6 +15,7 @@ export default defineConfig({ plugins: [react()], server: { port: 9000, + host: "0.0.0.0", }, build: { // Relative to the root diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b956165 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,57 @@ +version: "3.8" +services: + db: + image: postgres:14 + restart: unless-stopped + env_file: + - .env + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - db-data:/var/lib/postgresql/data + ports: + - "${DB_HOST_PORT:-5432}:5432" + + server: + build: ./server + command: npm run start:dev + volumes: + - ./server:/usr/src/app + - /usr/src/app/node_modules + environment: + NODE_ENV: ${NODE_ENV} + NODE_PORT: ${NODE_PORT} + DB_HOST: db + DB_PORT: ${DB_PORT} + DB_NAME: ${DB_NAME} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + CLIENT_HOST: ${CLIENT_HOST} + env_file: + - .env + depends_on: + - db + ports: + - "3000:3000" + + client: + build: ./client + command: npm run dev + volumes: + - ./client:/usr/src/app + - /usr/src/app/node_modules + environment: + PORT: ${CLIENT_PORT} + HOST: ${HOST} + env_file: + - .env + - ./client/.env + depends_on: + - server + ports: + - "9000:9000" + +volumes: + db-data: diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..1ec5429 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,6 @@ +node_modules +.env +dist +coverage +uploads +.DS_Store diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..cbe56ab --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18 +WORKDIR /usr/src/app +COPY package*.json ./ +RUN npm install +COPY . . +EXPOSE 3000 +CMD ["npm", "run", "start:dev"] From ae20d3bc4b18bda14e37bc2e0226769e232f0089 Mon Sep 17 00:00:00 2001 From: AngyDev Date: Sun, 22 Feb 2026 21:40:24 +0000 Subject: [PATCH 2/3] fix: update file upload handling in objects API --- server/.prettierrc | 10 ++++++++++ server/src/api/objects.js | 11 +++++++---- server/src/routes/objects.js | 3 ++- 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 server/.prettierrc diff --git a/server/.prettierrc b/server/.prettierrc new file mode 100644 index 0000000..b5b527d --- /dev/null +++ b/server/.prettierrc @@ -0,0 +1,10 @@ +{ + "singleQuote": false, + "trailingComma": "all", + "semi": true, + "tabWidth": 2, + "arrowParens": "always", + "useTabs": false, + "bracketSpacing": true, + "printWidth": 150 +} \ No newline at end of file diff --git a/server/src/api/objects.js b/server/src/api/objects.js index db68faf..b062482 100644 --- a/server/src/api/objects.js +++ b/server/src/api/objects.js @@ -90,13 +90,16 @@ const uploadFileAndSaveObject = errorHandler(async (req, res) => { // Uploads the file in the server folder await uploadFile(req, res); - if (req.file === undefined) { + // Get the file from formidable (req.files is an object with arrays) + const fileArray = req.files?.file; + if (!fileArray || fileArray.length === 0) { throw new HttpError(400, "Please upload a file!"); } - const filepath = req.params.projectId + "/" + req.file.originalname; - const objectId = req.body.id; - const objectName = req.body.objectName; + const file = fileArray[0]; + const filepath = req.params.projectId + "/" + file.originalFilename; + const objectId = req.body.id?.[0] || req.body.id; + const objectName = req.body.objectName?.[0] || req.body.objectName; const projectId = req.params.projectId; // Checks if the object is present in the database diff --git a/server/src/routes/objects.js b/server/src/routes/objects.js index 92c0e45..c2d59c0 100644 --- a/server/src/routes/objects.js +++ b/server/src/routes/objects.js @@ -1,12 +1,13 @@ const express = require("express"); const api = require("../api/objects"); +const uploadFile = require("../middleware/upload"); const router = express.Router(); router.get("/object/:id", api.getObjectById); // router.get("/objects/path/:projectId/", api.getObjectsPathByProjectId); router.get("/objects/:projectId/", api.getObjectsByProjectId); -router.post("/upload/:projectId", api.uploadFileAndSaveObject); +router.post("/upload/:projectId", uploadFile, api.uploadFileAndSaveObject); router.delete("/object/:id", api.deleteObject); module.exports = router; From 7a77325d70df5569dc8f4c3154c3b0fafe0f8cc0 Mon Sep 17 00:00:00 2001 From: AngyDev Date: Sun, 22 Feb 2026 22:05:16 +0000 Subject: [PATCH 3/3] fix: enhance file upload handling and directory management in objects API --- docker-compose.yml | 1 + server/src/api/objects.js | 11 +++++---- server/src/index.js | 3 +++ server/src/middleware/upload.js | 42 ++++++++++++++++++++------------- server/src/routes/objects.js | 3 +-- 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b956165..6b60f72 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,7 @@ services: environment: NODE_ENV: ${NODE_ENV} NODE_PORT: ${NODE_PORT} + FILE_PATH: /usr/src/app/src DB_HOST: db DB_PORT: ${DB_PORT} DB_NAME: ${DB_NAME} diff --git a/server/src/api/objects.js b/server/src/api/objects.js index b062482..08ac8f0 100644 --- a/server/src/api/objects.js +++ b/server/src/api/objects.js @@ -5,6 +5,7 @@ const { errorHandler } = require("../utils"); const { HttpError } = require("../error"); const uploadFile = require("../middleware/upload"); const fs = require("fs"); +const path = require("path"); /** * Get Object by id @@ -97,9 +98,11 @@ const uploadFileAndSaveObject = errorHandler(async (req, res) => { } const file = fileArray[0]; - const filepath = req.params.projectId + "/" + file.originalFilename; - const objectId = req.body.id?.[0] || req.body.id; - const objectName = req.body.objectName?.[0] || req.body.objectName; + // formidable v3 uses 'newFilename', v2 uses path, filepath, newFilename + const filename = file.newFilename || path.basename(file.filepath) || file.filename; + const filepath = req.params.projectId + "/" + filename; + const objectId = Array.isArray(req.body.id) ? req.body.id[0] : req.body.id; + const objectName = Array.isArray(req.body.objectName) ? req.body.objectName[0] : req.body.objectName; const projectId = req.params.projectId; // Checks if the object is present in the database @@ -110,7 +113,7 @@ const uploadFileAndSaveObject = errorHandler(async (req, res) => { await ObjectsController.saveObject(objectId, objectName, projectId, filepath); } - return { message: "Uploaded the file successfully: " + req.file.originalname }; + return { message: "Uploaded the file successfully: " + filename }; }); module.exports = { getObjectById, getObjectsPathByProjectId, deleteObject, uploadFileAndSaveObject, getObjectsByProjectId }; diff --git a/server/src/index.js b/server/src/index.js index f15253b..30d67e2 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -68,6 +68,9 @@ app.get("/", (req, res) => { res.send("Hello World"); }); +// Serve static files before auth middleware +app.use("/uploads", express.static(path.join(__dirname, "/public/uploads"))); + app.use("/api/", require("./routes/auth")); app.use(verifyToken); app.use("/api/", require("./routes/users")); diff --git a/server/src/middleware/upload.js b/server/src/middleware/upload.js index 854a98f..e5628e8 100644 --- a/server/src/middleware/upload.js +++ b/server/src/middleware/upload.js @@ -2,26 +2,36 @@ const formidable = require("formidable"); const fs = require("fs"); const path = require("path"); -const uploadFileMiddleware = (req, res, next) => { - const dir = path.join(process.env.FILE_PATH, "/public/uploads/", req.params.projectId); +const uploadFileMiddleware = (req, res) => { + return new Promise((resolve, reject) => { + const baseDir = process.env.FILE_PATH || process.cwd(); + const projectDir = req.params.projectId; + const dir = path.join(baseDir, "public", "uploads", projectDir); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } + // Ensure directory exists + if (!fs.existsSync(dir)) { + try { + fs.mkdirSync(dir, { recursive: true }); + } catch (err) { + console.error("Failed to create directory:", err); + return reject(err); + } + } - const form = new formidable.IncomingForm({ - uploadDir: dir, - keepExtensions: true, // Keep original file extensions - }); + const form = new formidable.IncomingForm({ + uploadDir: dir, + keepExtensions: true, + }); - form.parse(req, (err, fields, files) => { - if (err) { - return next(err); - } + form.parse(req, (err, fields, files) => { + if (err) { + return reject(err); + } - req.files = files; // Attach files to the request object for further processing - req.body = fields; // Attach other form fields if needed - next(); + req.files = files; + req.body = fields; + resolve(); + }); }); }; diff --git a/server/src/routes/objects.js b/server/src/routes/objects.js index c2d59c0..92c0e45 100644 --- a/server/src/routes/objects.js +++ b/server/src/routes/objects.js @@ -1,13 +1,12 @@ const express = require("express"); const api = require("../api/objects"); -const uploadFile = require("../middleware/upload"); const router = express.Router(); router.get("/object/:id", api.getObjectById); // router.get("/objects/path/:projectId/", api.getObjectsPathByProjectId); router.get("/objects/:projectId/", api.getObjectsByProjectId); -router.post("/upload/:projectId", uploadFile, api.uploadFileAndSaveObject); +router.post("/upload/:projectId", api.uploadFileAndSaveObject); router.delete("/object/:id", api.deleteObject); module.exports = router;