diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dd5e810 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,49 @@ +# Node modules +node_modules +npm-debug.log +yarn-error.log + +# Build outputs +.output +.nuxt +dist +.cache + +# Environment files +.env +.env.local +.env.*.local + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Git +.git +.gitignore +.github + +# Documentation +README.md +docs + +# Test files +*.test.js +*.spec.js +coverage + +# Logs +logs +*.log + +# Docker files (if building) +Dockerfile +.dockerignore +docker-compose.yml diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 0000000..f12830f --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,23 @@ +# Environment Variables Example +# Copy this file to .env and fill in your values +# This file is used by both development and Docker + +# Application +NODE_ENV=development + +# Keycloak Configuration +KEYCLOAK_ISSUER=https://your-keycloak-server/realms/your-realm +KEYCLOAK_CLIENT_ID=your-client-id +KEYCLOAK_CLIENT_SECRET=your-client-secret + +# API Configuration +NUXT_PUBLIC_API_BASE_URL=https://your-api-server.com +NUXT_PUBLIC_AUTH_URL=http://localhost:3005 + +# Database (if needed) +# DATABASE_URL=postgresql://user:password@postgres:5432/dbname + +# Redis (if using Redis for session store) +# REDIS_HOST=redis +# REDIS_PORT=6379 +# REDIS_PASSWORD=your-redis-password diff --git a/.gitignore b/.gitignore index 97e75a5..5fb2102 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ node_modules .cache .output .env +.env.local +.env.*.local dist -Dockerfile -docker-compose.yml \ No newline at end of file +# Dockerfile and docker-compose.yml should be committed +# Dockerfile +# docker-compose.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9bd289c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +# Dockerfile for Nuxt.js Application +# Multi-stage build for optimized image size + +# Stage 1: Build Stage +FROM node:20-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy application files +COPY . . + +# Build the application +RUN npm run build + +# Stage 2: Production Stage +FROM node:20-alpine AS runner + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install production dependencies only (skip postinstall script) +RUN npm ci --omit=dev --ignore-scripts + +# Copy built application from builder stage +COPY --from=builder /app/.output /app/.output +COPY --from=builder /app/.nuxt /app/.nuxt +COPY --from=builder /app/nuxt.config.ts /app/nuxt.config.ts +COPY --from=builder /app/node_modules /app/node_modules + +# Create non-root user for security +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nuxtjs -u 1001 && \ + chown -R nuxtjs:nodejs /app + +# Switch to non-root user +USER nuxtjs + +# Expose port +EXPOSE 3000 + +# Set environment variables +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" + +# Start the application +CMD ["node", ".output/server/index.mjs"] diff --git a/README.Docker.md b/README.Docker.md new file mode 100644 index 0000000..1f9edad --- /dev/null +++ b/README.Docker.md @@ -0,0 +1,179 @@ +# Docker Deployment Guide + +## 📦 Prerequisites + +- Docker Desktop installed +- Docker Compose installed (included with Docker Desktop) + +## 🚀 Quick Start + +### 1. Build the Docker Image + +```bash +docker build -t antrean-operasi:latest . +``` + +### 2. Run with Docker Compose + +```bash +# Make sure .env file exists with your configuration +# Docker Compose will automatically use .env file + +docker-compose up -d +``` + +### 3. Run without Docker Compose + +```bash +docker run -d \ + --name antrean-operasi \ + -p 3005:3000 \ + -e KEYCLOAK_ISSUER=https://your-keycloak/realms/your-realm \ + -e KEYCLOAK_CLIENT_ID=your-client-id \ + -e KEYCLOAK_CLIENT_SECRET=your-client-secret \ + -e API_BASE_URL=https://your-api.com \ + -e AUTH_URL=http://localhost:3005 \ + antrean-operasi:latest +``` + +## 🔧 Available Commands + +### Build image +```bash +docker build -t antrean-operasi:latest . +``` + +### Start containers +```bash +docker-compose up -d +``` + +### Stop containers +```bash +docker-compose down +``` + +### View logs +```bash +docker-compose logs -f antrean-operasi +``` + +### Rebuild and restart +```bash +docker-compose up -d --build +``` + +### Access container shell +```bash +docker exec -it antrean-operasi sh +``` + +## 🌐 Access Application + +After starting the container, access the application at: +- **http://localhost:3005** + +## 📝 Environment Variables + +Required environment variables (set in `.env` file): + +| Variable | Description | Example | +|----------|-------------|---------| +| `KEYCLOAK_ISSUER` | Keycloak realm URL | `https://keycloak.example.com/realms/myrealm` | +| `KEYCLOAK_CLIENT_ID` | Keycloak client ID | `antrean-operasi-client` | +| `KEYCLOAK_CLIENT_SECRET` | Keycloak client secret | `your-secret-here` | +| `API_BASE_URL` | Backend API URL | `https://api.example.com` | +| `AUTH_URL` | Application auth URL | `http://localhost:3005` | + +## 🐳 Docker Hub (Optional) + +### Tag and Push to Docker Hub + +```bash +# Tag the image +docker tag antrean-operasi:latest yourusername/antrean-operasi:latest + +# Login to Docker Hub +docker login + +# Push to Docker Hub +docker push yourusername/antrean-operasi:latest +``` + +### Pull from Docker Hub + +```bash +docker pull yourusername/antrean-operasi:latest +``` + +## 🔍 Troubleshooting + +### Check container status +```bash +docker ps -a +``` + +### View detailed logs +```bash +docker logs antrean-operasi +``` + +### Check container resource usage +```bash +docker stats antrean-operasi +``` + +### Restart container +```bash +docker restart antrean-operasi +``` + +### Remove all containers and images +```bash +docker-compose down --rmi all --volumes +``` + +## 🏗️ Production Recommendations + +1. **Use environment-specific configs**: Create separate `.env.production` files +2. **Enable HTTPS**: Use a reverse proxy (Nginx, Traefik) with SSL certificates +3. **Use Docker secrets**: For sensitive data in production +4. **Set resource limits**: Add memory and CPU limits in docker-compose.yml +5. **Use Redis**: Replace in-memory session store with Redis +6. **Setup monitoring**: Add health checks and monitoring tools +7. **Regular backups**: Backup mounted volumes and data + +### Example Production docker-compose.yml with limits: + +```yaml +services: + antrean-operasi: + # ... other config ... + deploy: + resources: + limits: + cpus: '2' + memory: 2G + reservations: + cpus: '1' + memory: 1G +``` + +## 📊 Health Check + +The container includes a health check endpoint. Check container health: + +```bash +docker inspect --format='{{json .State.Health}}' antrean-operasi +``` + +## 🔐 Security Notes + +- Never commit `.env.docker` to version control +- Use Docker secrets for production deployments +- Run container as non-root user (already configured) +- Regularly update base images for security patches + +## 📞 Support + +For issues or questions, please contact the development team. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e5536e3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.8' + +services: + antrean-operasi: + build: + context: . + dockerfile: Dockerfile + container_name: antrean-operasi + ports: + - "3005:3000" + env_file: + - .env + environment: + - NODE_ENV=production + - HOST=0.0.0.0 + - PORT=3000 + volumes: + # Mount for persistent data + - ./data:/app/data + restart: unless-stopped + networks: + - antrean-network + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +networks: + antrean-network: + driver: bridge + +volumes: + data: diff --git a/nuxt.config.ts b/nuxt.config.ts index ee798d5..1688a22 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -89,22 +89,25 @@ export default defineNuxtConfig({ // } }, runtimeConfig: { + // Nuxt will automatically map NUXT_* environment variables + // NUXT_KEYCLOAK_CLIENT_ID → keycloakClientId authSecret: process.env.NUXT_AUTH_SECRET, - keycloakClientId: process.env.KEYCLOAK_CLIENT_ID, - keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET, - keycloakIssuer: process.env.KEYCLOAK_ISSUER, - keycloakLogoutUri: process.env.KEYCLOAK_LOGOUT_URI, // Optional: custom logout URI - postLogoutRedirectUri: process.env.POST_LOGOUT_REDIRECT_URI, // Optional: custom post-logout redirect URI + keycloakClientId: process.env.NUXT_KEYCLOAK_CLIENT_ID || process.env.KEYCLOAK_CLIENT_ID, + keycloakClientSecret: process.env.NUXT_KEYCLOAK_CLIENT_SECRET || process.env.KEYCLOAK_CLIENT_SECRET, + keycloakIssuer: process.env.NUXT_KEYCLOAK_ISSUER || process.env.KEYCLOAK_ISSUER, + keycloakLogoutUri: process.env.NUXT_KEYCLOAK_LOGOUT_URI || process.env.KEYCLOAK_LOGOUT_URI, + postLogoutRedirectUri: process.env.NUXT_POST_LOGOUT_REDIRECT_URI || process.env.POST_LOGOUT_REDIRECT_URI, public: { - authUrl: process.env.AUTH_ORIGIN, - // authUrl: process.env.AUTH_ORIGIN || "http://10.10.150.175:3001", - // authUrl: process.env.AUTH_ORIGIN || "http://localhost:3001", - wsBaseUrl: process.env.WS_BASE_URL || 'ws://10.10.150.100:8084/api/v1/ws', - baseUrl: process.env.BASE_URL || 'http://10.10.150.144:8080/api', - baseUrlGomed: process.env.BASE_URL_GOMED || 'https://gomed.rssa.my.id/api', - keycloakUrl: process.env.KEYCLOAK_ISSUER ? `${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token` : 'https://auth.rssa.top/realms/sandbox/protocol/openid-connect/token', - keycloakClientId: process.env.KEYCLOAK_CLIENT_ID || 'akbar-test', - keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET || 'FDyv3UYMgJOYPnvzXVVv6diRtcgEevKg', + // NUXT_PUBLIC_* variables are exposed to client-side + authUrl: process.env.NUXT_PUBLIC_AUTH_URL || process.env.AUTH_ORIGIN, + wsBaseUrl: process.env.NUXT_PUBLIC_WS_BASE_URL || process.env.WS_BASE_URL || 'ws://10.10.150.100:8084/api/v1/ws', + baseUrl: process.env.NUXT_PUBLIC_BASE_URL || process.env.BASE_URL || 'http://10.10.150.144:8080/api', + baseUrlGomed: process.env.NUXT_PUBLIC_BASE_URL_GOMED || process.env.BASE_URL_GOMED || 'https://gomed.rssa.my.id/api', + keycloakUrl: process.env.NUXT_KEYCLOAK_ISSUER ? `${process.env.NUXT_KEYCLOAK_ISSUER}/protocol/openid-connect/token` : + process.env.KEYCLOAK_ISSUER ? `${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token` : + 'https://auth.rssa.top/realms/sandbox/protocol/openid-connect/token', + keycloakClientId: process.env.NUXT_KEYCLOAK_CLIENT_ID || process.env.KEYCLOAK_CLIENT_ID || 'akbar-test', + keycloakClientSecret: process.env.NUXT_KEYCLOAK_CLIENT_SECRET || process.env.KEYCLOAK_CLIENT_SECRET || 'FDyv3UYMgJOYPnvzXVVv6diRtcgEevKg', keycloakClientUuid: process.env.KEYCLOAK_CLIENT_UUID, keycloakAdminRealmUrl: process.env.KEYCLOAK_ADMIN_REALM_URL }, diff --git a/package-lock.json b/package-lock.json index a7f145a..3de5af0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "apexcharts": "4.5.0", "axios": "^1.9.0", "axios-mock-adapter": "^2.1.0", - "better-sqlite3": "^12.6.2", "gsap": "^3.14.2", "jsonwebtoken": "^9.0.2", "lodash-es": "^4.17.21", @@ -3926,6 +3925,8 @@ "integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==", "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -3969,6 +3970,8 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3994,6 +3997,8 @@ } ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -4004,6 +4009,8 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -4916,6 +4923,8 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -4931,6 +4940,8 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4.0.0" } @@ -5651,6 +5662,8 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "license": "(MIT OR WTFPL)", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -6004,7 +6017,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -6168,7 +6183,9 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/glob": { "version": "10.4.5", @@ -7471,6 +7488,8 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10" }, @@ -7548,7 +7567,9 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/mlly": { "version": "1.7.4", @@ -7648,7 +7669,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/neo-async": { "version": "2.6.2", @@ -7940,6 +7963,8 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "semver": "^7.3.5" }, @@ -9573,6 +9598,8 @@ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -9599,6 +9626,8 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -9798,6 +9827,8 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "peer": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -9812,7 +9843,9 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/rc9": { "version": "2.1.2", @@ -10512,7 +10545,9 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -10533,6 +10568,8 @@ } ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -10875,6 +10912,8 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11061,6 +11100,8 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -11072,13 +11113,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/tar-fs/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11093,6 +11138,8 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -11286,6 +11333,8 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, diff --git a/package.json b/package.json index 4b3b6ee..05bae73 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "apexcharts": "4.5.0", "axios": "^1.9.0", "axios-mock-adapter": "^2.1.0", - "better-sqlite3": "^12.6.2", "gsap": "^3.14.2", "jsonwebtoken": "^9.0.2", "lodash-es": "^4.17.21", diff --git a/server/api/health.get.ts b/server/api/health.get.ts new file mode 100644 index 0000000..7dd915e --- /dev/null +++ b/server/api/health.get.ts @@ -0,0 +1,33 @@ +// server/api/health.get.ts +// Health check endpoint for Docker and monitoring + +export default defineEventHandler(async (event) => { + try { + // Basic health check + const health = { + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: process.env.NODE_ENV || 'development', + memory: { + used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024), + total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024), + unit: 'MB' + } + }; + + return { + success: true, + ...health + }; + } catch (error: any) { + // If health check fails, return 500 + setResponseStatus(event, 500); + return { + success: false, + status: 'unhealthy', + error: error.message, + timestamp: new Date().toISOString() + }; + } +});