Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .do/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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: /api
- path: /health
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
- 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.
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
@@ -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'])
49 changes: 49 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
3 changes: 3 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Runtime config (generated at container start in dist/)
/dist/runtime-config.js
10 changes: 8 additions & 2 deletions app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
26 changes: 26 additions & 0 deletions app/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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;'
2 changes: 2 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

<body>
<div id="root"></div>
<!-- Runtime API URL injection (set via VITE_API_URL env var in Docker) -->
<script src="/runtime-config.js"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions app/public/runtime-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Runtime configuration — generated by docker-entrypoint.sh in production.
// For local development, VITE_API_URL is set via .env or defaults to localhost:8000.
30 changes: 30 additions & 0 deletions deploy/aws/apprunner.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# AWS App Runner configuration
# Deploy with: aws apprunner create-service --cli-input-json file://apprunner.json
#
# This YAML is a reference. Convert to JSON for the CLI or use the console.
Comment on lines +2 to +4

ServiceName: finmind-backend
SourceConfiguration:
ImageRepository:
ImageIdentifier: <ECR_IMAGE_URI>
ImageRepositoryType: ECR
ImageConfiguration:
Port: "8000"
RuntimeEnvironmentVariables:
DATABASE_URL: <your-database-url>
REDIS_URL: <your-redis-url>
JWT_SECRET: <your-jwt-secret>
LOG_LEVEL: INFO
GEMINI_MODEL: gemini-1.5-flash
StartCommand: "sh -c 'python -m flask --app wsgi:app init-db && gunicorn --workers=2 --threads=4 --bind 0.0.0.0:8000 wsgi:app'"
AutoDeploymentsEnabled: true
InstanceConfiguration:
Cpu: "0.25 vCPU"
Memory: "0.5 GB"
HealthCheckConfiguration:
Protocol: HTTP
Path: /health
Interval: 10
Timeout: 5
HealthyThreshold: 1
UnhealthyThreshold: 5
Loading