diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..aa10a0a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +frontend/node_modules +backend/node_modules +frontend/dist +backend/dist +backend/db.sqlite +backend/uploads +*.log +.DS_Store +.env \ No newline at end of file diff --git a/.gitignore b/.gitignore index 69b8d5f..d4334a5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,26 +8,19 @@ dist/ frontend/dist/ backend/dist/ -# SQLite database and uploads -backend/db.sqlite -backend/uploads/ +# Persistent backend data (database and uploads) +backend/data/ # Logs logs/ *.log npm-debug.log* yarn-debug.log* -pnpm-debug.log* # Editor directories and files .vscode/ .idea/ .DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? # Environment files (if any) .env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..21dfd21 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Build frontend +FROM node:24-alpine AS frontend +WORKDIR /app/frontend +COPY frontend/package*.json ./ +RUN npm install +COPY frontend ./ +RUN npm run build + +# Build backend +FROM node:24-alpine AS backend +WORKDIR /app +COPY backend/package*.json ./backend/ +COPY backend/ ./backend/ +RUN cd backend && npm install + +RUN mkdir -p /data/uploads && chmod -R 777 /data + +# Copy built frontend into backend +COPY --from=frontend /app/frontend/dist ./frontend/dist + +WORKDIR /app/backend +EXPOSE 3000 + +ENV DATA_DIR=/data + +CMD ["node", "index.js"] \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index 1dedad5..5e2166d 100644 --- a/backend/index.js +++ b/backend/index.js @@ -6,13 +6,19 @@ const multer = require('multer'); const fs = require('fs'); const app = express(); -const dbPath = path.join(__dirname, 'db.sqlite'); -const db = new sqlite3.Database(dbPath); -const upload = multer({ dest: path.join(__dirname, 'uploads/') }); +// Use DATA_DIR env var if set, otherwise use local "data" folder +const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, 'data'); + +const dbPath = path.join(DATA_DIR, 'db.sqlite'); +const db = new sqlite3.Database(dbPath); +const upload = multer({ dest: path.join(DATA_DIR, 'uploads/') }); app.use(cors()); app.use(express.json()); -app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); +app.use('/uploads', express.static(path.join(DATA_DIR, 'uploads'))); + +// Create uploads directory if it doesn't exist +fs.mkdirSync(path.join(DATA_DIR, 'uploads'), { recursive: true }); // Initialize database and ensure counters table exists db.serialize(() => { @@ -38,7 +44,6 @@ app.get('/api/counters', (req, res) => { }); }); - // Create a new counter with image app.post('/api/counters', upload.single('image'), (req, res) => { const { name = 'Counter', value = 0 } = req.body; @@ -97,12 +102,20 @@ const clientBuildPath = path.join(__dirname, '..', 'frontend', 'dist'); app.use(express.static(clientBuildPath)); // For SPA: serve index.html for any unknown route (after API and uploads) -// app.get('*', (req, res) => { -// if (req.path.startsWith('/api') || req.path.startsWith('/uploads')) return res.status(404).end(); -// res.sendFile(path.join(clientBuildPath, 'index.html')); -// }); +app.get('/*path', (req, res) => { + if (req.path.startsWith('/api') || req.path.startsWith('/uploads')) return res.status(404).end(); + res.sendFile(path.join(clientBuildPath, 'index.html')); +}); const PORT = 3000; app.listen(PORT, () => { console.log(`API server running on http://localhost:${PORT}`); +}); + +process.on('SIGTERM', () => { + console.log('SIGTERM received, shutting down gracefully...'); + db.close(() => { + console.log('Database connection closed.'); + process.exit(0); + }); }); \ No newline at end of file