diff --git a/.do/app.yaml b/.do/app.yaml new file mode 100644 index 00000000..2c803fd4 --- /dev/null +++ b/.do/app.yaml @@ -0,0 +1,77 @@ +spec: + name: finmind + region: nyc + services: + - name: backend + dockerfile_path: packages/backend/Dockerfile + github: + repo: rohitdash08/FinMind + branch: main + deploy_on_push: true + http_port: 8000 + instance_count: 1 + instance_size_slug: basic-xxs + routes: + - path: /health + - path: /auth + - path: /expenses + - path: /bills + - path: /reminders + - path: /dashboard + - path: /insights + health_check: + http_path: /health + initial_delay_seconds: 15 + period_seconds: 10 + run_command: | + sh -c "python -m flask --app wsgi:app init-db && gunicorn --workers=2 --threads=4 --bind 0.0.0.0:8000 wsgi:app" + envs: + - key: DATABASE_URL + scope: RUN_TIME + value: ${db.DATABASE_URL} + - key: REDIS_URL + scope: RUN_TIME + type: SECRET + value: CHANGE_ME_SET_EXTERNAL_REDIS_URL + # DigitalOcean App Platform does not include managed Redis. + # Create a DO Managed Redis database or use Upstash (free tier), + # then replace this value in the App dashboard. + - key: JWT_SECRET + scope: RUN_TIME + type: SECRET + value: CHANGE_ME_SET_A_RANDOM_SECRET + - key: LOG_LEVEL + scope: RUN_TIME + value: INFO + - key: GEMINI_MODEL + scope: RUN_TIME + value: gemini-1.5-flash + + - name: frontend + dockerfile_path: app/Dockerfile + github: + repo: rohitdash08/FinMind + branch: main + deploy_on_push: true + http_port: 80 + instance_count: 1 + instance_size_slug: basic-xxs + routes: + - path: / + envs: + - key: BACKEND_URL + scope: RUN_TIME + value: CHANGE_ME_SET_BACKEND_PUBLIC_URL + # Set this to the backend service's public URL after first deploy. + # Example: https://finmind-backend-xxxxx.ondigitalocean.app + + databases: + - name: db + engine: PG + version: "16" + size: db-s-dev-database + num_nodes: 1 + + # Note: DigitalOcean App Platform does not offer managed Redis. + # Create a DigitalOcean Managed Redis database separately, or use + # Upstash (free tier available), then set REDIS_URL on the backend. diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..dde34cc5 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: python -m flask --app wsgi:app init-db && gunicorn --workers=2 --threads=4 --bind 0.0.0.0:$PORT wsgi:app diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 00000000..5b5c121e --- /dev/null +++ b/Tiltfile @@ -0,0 +1,37 @@ +# FinMind Tiltfile — local Kubernetes development + +# Build backend image (matches K8s manifest image reference) +docker_build('ghcr.io/rohitdash08/finmind-backend', './packages/backend', + live_update=[ + sync('./packages/backend/app', '/app/app'), + sync('./packages/backend/wsgi.py', '/app/wsgi.py'), + run('pip install -r requirements.txt', trigger=['./packages/backend/requirements.txt']), + ] +) + +# Build frontend image for local development. +# The K8s manifests use a separate nginx reverse proxy; this image can be +# deployed manually or used when a frontend K8s Deployment is added. +docker_build('ghcr.io/rohitdash08/finmind-frontend', './app') + +# Apply K8s manifests +k8s_yaml([ + 'deploy/k8s/namespace.yaml', + 'deploy/k8s/secrets.example.yaml', + 'deploy/k8s/app-stack.yaml', +]) + +# Resource grouping and dependencies +k8s_resource('postgres', labels=['database'], + port_forwards='5432:5432') + +k8s_resource('redis', labels=['database'], + port_forwards='6379:6379') + +k8s_resource('backend', labels=['app'], + port_forwards='8000:8000', + resource_deps=['postgres', 'redis']) + +k8s_resource('nginx', labels=['app'], + port_forwards='8080:80', + resource_deps=['backend']) diff --git a/app.json b/app.json new file mode 100644 index 00000000..c129152a --- /dev/null +++ b/app.json @@ -0,0 +1,49 @@ +{ + "name": "FinMind", + "description": "AI-powered personal finance manager", + "repository": "https://github.com/rohitdash08/FinMind", + "logo": "", + "keywords": ["finance", "budgeting", "flask", "react"], + "stack": "container", + "addons": [ + { + "plan": "heroku-postgresql:essential-0" + }, + { + "plan": "heroku-redis:mini" + } + ], + "env": { + "JWT_SECRET": { + "description": "Secret key for JWT token signing", + "generator": "secret" + }, + "DATABASE_URL": { + "description": "PostgreSQL connection URL (auto-set by addon)" + }, + "REDIS_URL": { + "description": "Redis connection URL (auto-set by addon)" + }, + "LOG_LEVEL": { + "description": "Logging level", + "value": "INFO" + }, + "GEMINI_API_KEY": { + "description": "Google Gemini API key for AI features", + "required": false + }, + "GEMINI_MODEL": { + "description": "Gemini model name", + "value": "gemini-1.5-flash" + } + }, + "formation": { + "web": { + "quantity": 1, + "size": "basic" + } + }, + "buildpacks": [], + "scripts": {}, + "environments": {} +} diff --git a/app/.gitignore b/app/.gitignore index a547bf36..44854035 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# Runtime config (generated at container start in dist/) +/dist/runtime-config.js diff --git a/app/Dockerfile b/app/Dockerfile index 2d1a3ad0..6e64b181 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -4,11 +4,17 @@ WORKDIR /app COPY package*.json ./ RUN npm ci || npm install COPY . . +ARG VITE_API_URL="" +ENV VITE_API_URL=${VITE_API_URL} RUN npm run build FROM nginx:alpine -# Copy custom nginx config for SPA fallback +# SPA nginx config COPY nginx.conf /etc/nginx/conf.d/default.conf +# Built frontend assets COPY --from=builder /app/dist /usr/share/nginx/html +# Runtime config entrypoint (can override API URL at container start) +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/app/docker-entrypoint.sh b/app/docker-entrypoint.sh new file mode 100755 index 00000000..484a7974 --- /dev/null +++ b/app/docker-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +# Determine the backend API URL for client-side JavaScript. +# Priority: BACKEND_URL > VITE_API_URL > empty (relative / same-origin) +API_URL="${BACKEND_URL:-${VITE_API_URL:-}}" + +# Ensure URL has scheme if set +if [ -n "$API_URL" ]; then + case "$API_URL" in + http://*|https://*) ;; + *) API_URL="https://${API_URL}" ;; + esac +fi + +# Inject runtime config — the frontend reads window.__FINMIND_API_URL__ +CONFIG_FILE="/usr/share/nginx/html/runtime-config.js" +if [ -n "$API_URL" ]; then + echo "window.__FINMIND_API_URL__ = \"${API_URL}\";" > "$CONFIG_FILE" + echo "Runtime config: API_URL=${API_URL}" +else + echo "// No API URL configured — using build-time VITE_API_URL or default" > "$CONFIG_FILE" + echo "Warning: No BACKEND_URL or VITE_API_URL set." +fi + +exec nginx -g 'daemon off;' diff --git a/app/index.html b/app/index.html index a8dd1672..2e86733f 100644 --- a/app/index.html +++ b/app/index.html @@ -15,6 +15,8 @@
+ +