diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1799afd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,47 @@ +version: '3.8' + +services: + frontend: + build: + context: . + dockerfile: packages/frontend/Dockerfile + ports: + - "8080:80" + environment: + - VITE_API_URL=http://localhost:3001/api + depends_on: + backend: + condition: service_healthy + restart: unless-stopped + + backend: + build: + context: . + dockerfile: packages/backend/Dockerfile + ports: + - "3001:3000" + environment: + - MONGODB_URI=mongodb://mongo:27017/rekordbox + - PORT=3000 + - NODE_ENV=production + depends_on: + mongo: + condition: service_healthy + restart: unless-stopped + + mongo: + image: mongo:latest + ports: + - "27017:27017" + volumes: + - mongodb_data:/data/db + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + restart: unless-stopped + +volumes: + mongodb_data: \ No newline at end of file diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile new file mode 100644 index 0000000..cd32ae7 --- /dev/null +++ b/packages/backend/Dockerfile @@ -0,0 +1,38 @@ +FROM node:20-alpine as builder + +WORKDIR /app + +# Copy root package files +COPY package*.json ./ +COPY packages/backend/package*.json ./packages/backend/ + +# Install dependencies +RUN npm install + +# Copy source code +COPY packages/backend/ ./packages/backend/ + +# Build the app +RUN cd packages/backend && npm run build + +# Production image +FROM node:20-alpine + +WORKDIR /app + +# Copy package files +COPY packages/backend/package*.json ./ + +# Install production dependencies only +RUN npm install --production + +# Copy built files from builder stage +COPY --from=builder /app/packages/backend/dist ./dist + +# Add health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost:3000/api/health || exit 1 + +EXPOSE 3000 + +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index f19dcab..20ffcf3 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -13,6 +13,16 @@ const port = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); +// Health check endpoint +app.get('/api/health', (req, res) => { + const mongoStatus = mongoose.connection.readyState === 1 ? 'connected' : 'disconnected'; + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + mongo: mongoStatus + }); +}); + // Connect to MongoDB mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/rekordbox') .then(() => console.log('Connected to MongoDB')) diff --git a/packages/backend/src/models/Song.ts b/packages/backend/src/models/Song.ts index cf7c6f0..19c9f64 100644 --- a/packages/backend/src/models/Song.ts +++ b/packages/backend/src/models/Song.ts @@ -47,7 +47,4 @@ const songSchema = new mongoose.Schema({ } }); -// Ensure index on id field -songSchema.index({ id: 1 }, { unique: true }); - export const Song = mongoose.model('Song', songSchema); \ No newline at end of file diff --git a/packages/frontend/Dockerfile b/packages/frontend/Dockerfile new file mode 100644 index 0000000..98bda3e --- /dev/null +++ b/packages/frontend/Dockerfile @@ -0,0 +1,31 @@ +FROM node:20-alpine as builder + +WORKDIR /app + +# Copy root package files +COPY package*.json ./ +COPY packages/frontend/package*.json ./packages/frontend/ + +# Install dependencies +RUN npm install + +# Copy source code +COPY packages/frontend/ ./packages/frontend/ + +# Build the app +RUN cd packages/frontend && npm run build + +# Production image +FROM nginx:alpine + +# Copy nginx configuration +COPY packages/frontend/nginx.conf /etc/nginx/conf.d/default.conf + +# Copy built files from builder stage +COPY --from=builder /app/packages/frontend/dist /usr/share/nginx/html + +# Add health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost:80/ || exit 1 + +EXPOSE 80 \ No newline at end of file diff --git a/packages/frontend/nginx.conf b/packages/frontend/nginx.conf new file mode 100644 index 0000000..d2ef42c --- /dev/null +++ b/packages/frontend/nginx.conf @@ -0,0 +1,33 @@ +server { + listen 80; + server_name localhost; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 10240; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript; + gzip_disable "MSIE [1-6]\."; + + root /usr/share/nginx/html; + index index.html; + + # Cache static assets + location /assets { + expires 1y; + add_header Cache-Control "public, no-transform"; + } + + # Handle SPA routing + location / { + try_files $uri $uri/ /index.html; + expires -1; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } +} \ No newline at end of file diff --git a/packages/frontend/src/services/api.ts b/packages/frontend/src/services/api.ts index bc32022..e050d78 100644 --- a/packages/frontend/src/services/api.ts +++ b/packages/frontend/src/services/api.ts @@ -1,6 +1,6 @@ import { Song, Playlist } from '../types/interfaces'; -const API_URL = 'http://localhost:3000/api'; +const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'; async function handleResponse(response: Response): Promise { if (!response.ok) {