{"id":46541248,"url":"https://github.com/gonzalez8/fintrack","last_synced_at":"2026-05-16T15:00:58.355Z","repository":{"id":340201469,"uuid":"1162142401","full_name":"Gonzalez8/fintrack","owner":"Gonzalez8","description":"Personal investment tracker with FIFO portfolio engine, dividends, tax reports and monthly savings — Django + React + Celery + Docker","archived":false,"fork":false,"pushed_at":"2026-05-15T18:01:02.000Z","size":5905,"stargazers_count":1,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-15T20:27:17.530Z","etag":null,"topics":["celery","django","docker","drf","nextjs","postgresql","react","redis","tailwindcss","typescript"],"latest_commit_sha":null,"homepage":"https://fintrack-phi-ten.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Gonzalez8.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"docs/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-19T23:08:58.000Z","updated_at":"2026-05-15T17:58:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Gonzalez8/fintrack","commit_stats":null,"previous_names":["gonzalez8/fintrack"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/Gonzalez8/fintrack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gonzalez8%2Ffintrack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gonzalez8%2Ffintrack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gonzalez8%2Ffintrack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gonzalez8%2Ffintrack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Gonzalez8","download_url":"https://codeload.github.com/Gonzalez8/fintrack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gonzalez8%2Ffintrack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33107564,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T04:41:52.686Z","status":"ssl_error","status_checked_at":"2026-05-16T04:41:52.009Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["celery","django","docker","drf","nextjs","postgresql","react","redis","tailwindcss","typescript"],"created_at":"2026-03-07T01:08:54.490Z","updated_at":"2026-05-16T15:00:58.348Z","avatar_url":"https://github.com/Gonzalez8.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Fintrack\n\n**Self-hosted investment portfolio tracker — built for privacy and clarity.**\n\nTrack your portfolio, transactions, dividends, interests and taxes from a single interface. No subscriptions, no data sharing, fully open source.\n\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![CI](https://img.shields.io/github/actions/workflow/status/Gonzalez8/fintrack/ci.yml?label=CI\u0026logo=github)](https://github.com/Gonzalez8/fintrack/actions/workflows/ci.yml)\n[![Django](https://img.shields.io/badge/Django-5.1-092E20?logo=django)](https://www.djangoproject.com/)\n[![Next.js](https://img.shields.io/badge/Next.js-16-000000?logo=nextdotjs)](https://nextjs.org/)\n[![React](https://img.shields.io/badge/React-19-61DAFB?logo=react)](https://react.dev/)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript)](https://www.typescriptlang.org/)\n[![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker)](https://www.docker.com/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/CONTRIBUTING.md)\n\n\u003c/div\u003e\n\n---\n\n## Screenshots\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/screenshots/01-dashboard.png\" alt=\"Dashboard\" width=\"49%\" /\u003e\n  \u003cimg src=\".github/screenshots/02-cartera.png\" alt=\"Portfolio\" width=\"49%\" /\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/screenshots/03-activos.png\" alt=\"Assets\" width=\"49%\" /\u003e\n  \u003cimg src=\".github/screenshots/04-cuentas.png\" alt=\"Accounts\" width=\"49%\" /\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/screenshots/05-operaciones.png\" alt=\"Transactions\" width=\"49%\" /\u003e\n  \u003cimg src=\".github/screenshots/06-dividendos.png\" alt=\"Dividends\" width=\"49%\" /\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/screenshots/08-ahorro.png\" alt=\"Monthly Savings\" width=\"49%\" /\u003e\n  \u003cimg src=\".github/screenshots/09-fiscal.png\" alt=\"Tax Report\" width=\"49%\" /\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/screenshots/10-cartera-detalle.png\" alt=\"Position detail chart\" width=\"49%\" /\u003e\n  \u003cimg src=\".github/screenshots/11-activo-detalle.png\" alt=\"Asset price history chart\" width=\"49%\" /\u003e\n\u003c/p\u003e\n\n---\n\n## Features\n\n### Portfolio Management\n- **Multi-asset support** — Stocks, ETFs, Funds, Crypto\n- **Real-time pricing** — Automatic updates via Yahoo Finance\n- **Cost basis engines** — FIFO, LIFO, and Weighted Average Cost (WAC)\n- **Position tracking** — Unrealized P\u0026L, cost basis, market value per position\n\n### Transaction Tracking\n- **Buy / Sell / Gift** transactions with commission and tax support\n- **Dividend tracking** — Gross, withholding tax, administration / custody fees, net and withholding rate per asset\n- **Interest income** — Date-range based tracking per account, with optional explicit withholding tax (NULL = inferred from gross/net, 0 = confirmed no withholding) and fees\n- **Import deduplication** — Hash-based duplicate detection\n\n### Accounts \u0026 Snapshots\n- **Multiple account types** — Operativa, Ahorro, Inversion, Depositos, Alternativos\n- **Balance snapshots** — Historical balance tracking with auto-sync\n- **Portfolio snapshots** — Periodic automated snapshots via Celery Beat\n- **Bulk snapshot** — Update all account balances in one action\n\n### Reports \u0026 Analytics\n- **Year summary** — Dividends, interests, realized P\u0026L, total income by year\n- **Patrimonio evolution** — Total net worth over time (cash + investments)\n- **Portfolio evolution** — Market value, cost basis, unrealized P\u0026L charts\n- **Monthly savings** — Cashflow and savings rate analysis\n- **Annual savings** — Yearly savings aggregates and breakdown\n- **Savings goals** — Target-based goals with 3-scenario projections (conservative, average, optimistic)\n- **CSV exports** — Transactions, dividends, and interests\n\n### Tax Reporting\n\nFintrack ships **two layers** of tax reporting:\n\n#### Country-agnostic (always available)\n- **Year summary** — Dividends, interests and realized P\u0026L by year\n- **Withholdings by country** — Foreign vs. local breakdown for any residency\n- **CSV export** — Per-year transactions, dividends and interests\n\n#### Country-specific tax-declaration assistant\n- **Fiscal residence in Settings** — Pick your country (`Settings.tax_country`, ISO 3166-1 alpha-2). Default `ES`. The declaration tab only appears when an adapter exists for your country.\n- **Spain (Modo Renta)** — Maps your data directly to the AEAT Renta Web boxes (Modelo 100):\n  - Interests, dividends (Spanish vs. foreign split), foreign double-taxation deduction (per-country, with the bilateral treaty cap — default 15%, configurable via `Settings.tax_treaty_limits`), capital gains\n  - Row-by-row declarable share sales with one-click \"Copy row\" (Entity / Transmission / Acquisition tab-separated)\n  - Final summary card with \"Copy all\" payload for the whole return\n  - Validation warnings — sales without cost basis, dividends without tax country, `gross ≠ net + withholding + fees` mismatches\n- **Other countries** — Pluggable. Adding a country only touches its own adapter module. See [ADR-007: Per-Country Tax Adapter Pattern](docs/adr/007-tax-adapter-pattern.md) for the recipe and design rationale.\n\n### Data Management\n- **Snapshot scheduling** — Configurable auto-snapshot frequency (15min to 24h)\n- **Data retention** — Automated purge of old snapshots (configurable: 1y, 5y, 10y, never)\n- **Storage monitoring** — Per-user database space tracking\n- **Bulk snapshots** — Update all account balances at once\n- **JSON backup/restore** — Full data export and import\n\n### Payroll Tracking\n- **Digitised payslips** — Track monthly net, gross, Social Security contributions and income tax withheld per employer. Feeds the Spanish Modo Renta tab as \"Rendimientos del trabajo\".\n- **Best-effort PDF parser (experimental)** — Upload a Spanish payslip PDF and Fintrack extracts gross, SS, IRPF, net, base IRPF and employer info to pre-fill the form. Suggestion-only, you always review and confirm before saving. Falls back to manual entry on unrecognised templates or scanned PDFs.\n- **Employer registry** — Reusable employer records (name, CIF, SS account) referenced by every payslip. Inline employer creation from the payroll dialog.\n- **Soft conciliation** — A `gross − SS − IRPF − net` warning surfaces when the identity breaks (anticipos, embargos, dietas exentas, retribución en especie…), but never blocks saving — real payslips legitimately diverge from that identity.\n- See [ADR-008](docs/adr/008-payroll-and-pdf-parser.md) for the full design.\n\n### Security \u0026 Auth\n- **JWT in httpOnly cookies** — Access + refresh tokens, SameSite=Lax\n- **Google OAuth 2.0** — One-click login with automatic account creation\n- **Rate limiting** — Per-endpoint throttling (login, register, password change)\n- **Multi-tenancy** — Strict owner-based data isolation\n\n### Real Estate \u0026 Mortgage\n- **Property tracking** — Current value, purchase price, net equity per property\n- **Smart mortgage setup** — Enter loan amount, term (years), rate type (fixed/variable) and rate. Monthly payment, months paid, and outstanding balance auto-calculated from purchase date\n- **Interactive amortization table** — Click any row (past or future) to add an early amortization. Choose reduce payment or reduce term per event. Schedule recalculates instantly\n- **Multiple amortizations** — Add several events at different months, each cascading into the next\n- **Timeline chart** — Balance curve (original vs modified), event markers, current position\n- **Payment breakdown** — Principal vs interest stacked bars per month\n- **Mortgage summary** — Side-by-side comparison: original vs modified for payment, installments, term, end date, total to pay, total interest, with savings highlighted\n- **Amortization persistence** — Events stored in database, optimistic UI updates\n\n### Privacy Mode\n- **One-click toggle** — Eye icon in sidebar / mobile nav masks all personal monetary amounts\n- **Smart masking** — Only personal amounts hidden (balances, gains, dividends). Public data stays visible: asset prices, percentages, share quantities\n- **Persistent** — Stored in cookie, survives page reload\n\n### Internationalization\n- **5 languages** — Spanish, English, German, French, Italian\n\n---\n\n## Quick Start\n\n### Option 1: Docker Compose (development)\n\n```bash\ngit clone https://github.com/Gonzalez8/Fintrack.git \u0026\u0026 cd Fintrack\ncp .env.example .env\ndocker compose up\n```\n\n| Service | URL |\n|---|---|\n| App | `http://localhost:3000` |\n| API | `http://localhost:8000/api/` |\n| Swagger UI | `http://localhost:8000/api/schema/swagger-ui/` |\n| Django Admin | `http://localhost:8000/admin/` |\n\nCreate a superuser (optional):\n\n```bash\ndocker compose exec backend python manage.py createsuperuser\n```\n\n### Option 2: Production (Pre-built Images)\n\nNo source code needed — uses images from GitHub Container Registry.\n\n```bash\nmkdir fintrack \u0026\u0026 cd fintrack\ncurl -O https://raw.githubusercontent.com/Gonzalez8/Fintrack/main/docker-compose.prod.yml\ncurl -O https://raw.githubusercontent.com/Gonzalez8/Fintrack/main/.env.production.example\ncp .env.production.example .env\n# Edit .env with your values (DB_PASSWORD, DJANGO_SECRET_KEY, etc.)\ndocker compose -f docker-compose.prod.yml up -d\n```\n\nThe superuser is created automatically on first start (`admin` / `admin` by default — change in `.env`).\n\n\u003e Always set strong, unique values for `DB_PASSWORD`, `DJANGO_SECRET_KEY` and `DJANGO_SUPERUSER_PASSWORD` before deploying.\n\n### Option 3: Portainer\n\n1. In Portainer, go to **Stacks \u003e Add stack**\n2. Paste the contents of [`docker-compose.prod.yml`](docker-compose.prod.yml)\n3. Add the required [environment variables](#environment-variables)\n4. Click **Deploy the stack**\n\n### Option 4: Live Demo (Vercel, with or without backend)\n\nEnabling the demo flag adds a \"Try Demo\" button on the login page. Clicking it starts a [MSW (Mock Service Worker)](https://mswjs.io/) session with static data — no database needed. Real login and registration still work normally if a backend is configured.\n\n| Vercel Setting | Value |\n|---|---|\n| Root Directory | `frontend` |\n| Build Command | `npm run build` |\n| Environment Variable | `NEXT_PUBLIC_DEMO_MODE=true` |\n\n**Test demo locally:**\n\n```bash\ncd frontend \u0026\u0026 NEXT_PUBLIC_DEMO_MODE=true npm run dev\n```\n\n\u003e `NEXT_PUBLIC_DEMO_MODE=true` enables the demo button — it does **not** replace real auth. Demo sessions are identified by a `demo: true` flag in the JWT token, so they coexist with real users on the same deployment.\n\n---\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                        Browser                              │\n└──────────────────────────┬──────────────────────────────────┘\n                           │\n              ┌────────────▼────────────┐\n              │      Nginx (port 80)    │  Reverse proxy\n              └──┬──────────────────┬───┘\n                 │                  │\n          /api/* │ /admin/* /static/*  │ /*\n          ┌──────▼──────┐    ┌──────▼──────┐\n          │  Django 5.1 │    │  Next.js 16 │\n          │  Gunicorn   │    │  SSR + BFF  │\n          │  Port 8000  │    │  Port 3000  │\n          └──┬─────┬───┘    └─────────────┘\n             │     │\n    ┌────────▼┐  ┌─▼────────┐\n    │ Postgres │  │  Redis   │\n    │   :5432  │  │  :6379   │\n    └──────────┘  └─┬────┬───┘\n                    │    │\n          ┌─────────▼┐ ┌─▼──────────┐\n          │  Celery  │ │ Celery Beat │\n          │  Worker  │ │  Scheduler  │\n          └──────────┘ └─────────────┘\n```\n\n### BFF Pattern\n\nThe browser **never** calls the Django API directly. All requests flow through Next.js Route Handlers (`/api/proxy/*`), which:\n1. Read JWT from httpOnly cookies\n2. Forward the request to Django with the Authorization header\n3. Handle token refresh transparently\n4. Return the response to the browser\n\n### Rendering Strategy\n\n| Route Group    | Strategy           | Description                        |\n| -------------- | ------------------ | ---------------------------------- |\n| `(marketing)/` | SSG                | Landing page, static generation    |\n| `(auth)/`      | Client Components  | Login, Register forms              |\n| `(dashboard)/` | SSR + Streaming    | Server Components + React Suspense |\n\n---\n\n## Security \u0026 Authentication\n\n### JWT token flow\n\n```\nBrowser\n  ├── Cookie ──► access token  (httpOnly, SameSite=Lax)\n  └── Cookie ──► refresh token (httpOnly, SameSite=Lax)\n        │\n        └─► POST /api/auth/token/refresh/  ──►  new access token\n```\n\n- Refresh token rotates on every use and is blacklisted after rotation (replay-safe).\n- All requests attach Bearer token automatically; 401 triggers a single transparent retry.\n\n### Google OAuth2\n\n```\n1. Load Google GIS script on login page\n2. User clicks \"Continue with Google\"\n3. Google returns an ID token (credential)\n4. POST /api/auth/google/ { credential }\n5. Backend verifies token with Google's public keys\n6. Get or create user by email\n7. Issue JWT pair → login complete\n```\n\n**Setup:**\n1. [Create an OAuth 2.0 Client ID](https://console.cloud.google.com/apis/credentials) (Web application)\n2. Add your domain to **Authorized JavaScript origins**\n3. Set `GOOGLE_CLIENT_ID=\u003cyour-id\u003e` in `.env`\n\n---\n\n## Tech Stack\n\n| Layer | Technology |\n|---|---|\n| **Backend** | Django 5.1 · Django REST Framework 3.15 · PostgreSQL 16 · Celery 5.3 · Redis 7 |\n| **Auth** | djangorestframework-simplejwt · google-auth |\n| **Frontend** | Next.js 16 · React 19 · TypeScript 5 · Tailwind CSS v4 · shadcn/ui (Base UI) |\n| **State** | TanStack Query 5 (server state) |\n| **Charts** | Recharts 3 · Lightweight Charts 5 |\n| **i18n** | Custom i18n (es, en, de, fr, it) |\n| **Infra** | Docker Compose · GitHub Actions CI · GHCR |\n| **API Docs** | drf-spectacular (OpenAPI 3 / Swagger UI) |\n\n---\n\n## Project Structure\n\n```\nfintrack/\n├── backend/                       Django 5.1 + DRF\n│   ├── apps/\n│   │   ├── core/                  JWT auth, base models, health check\n│   │   ├── assets/                Assets, accounts, snapshots, settings\n│   │   ├── transactions/          Transaction, Dividend, Interest\n│   │   ├── portfolio/             Cost basis engine (FIFO/LIFO/WAC)\n│   │   ├── reports/               Tax summaries, evolution, savings, goals\n│   │   ├── realestate/            Property, Amortization, mortgage simulation\n│   │   └── importer/              JSON backup / restore\n│   └── config/\n│       ├── settings/              base.py · development.py · production.py\n│       ├── urls.py\n│       └── celery.py\n│\n├── frontend/                      Next.js 16 App Router\n│   └── src/\n│       ├── app/\n│       │   ├── (marketing)/       Landing page (SSG)\n│       │   ├── (auth)/            Login, Register (Client Components)\n│       │   ├── (dashboard)/       Protected pages (SSR + Streaming)\n│       │   └── api/               BFF Route Handlers (proxy to Django)\n│       ├── components/\n│       │   ├── ui/                shadcn/ui (Base UI + Tailwind v4)\n│       │   └── app/               Domain-specific components\n│       ├── lib/                   api-client, utils, mortgage-math, privacy\n│       ├── types/                 TypeScript interfaces\n│       ├── demo/                  MSW handlers (demo mode)\n│       └── i18n/                  Translations (es, en, de, fr, it)\n│\n├── .github/workflows/\n│   ├── ci.yml                     Backend tests + frontend typecheck\n│   └── docker-publish.yml         Build \u0026 push to GHCR\n├── docker-compose.yml             Development (6 services)\n├── docker-compose.prod.yml        Production (pre-built images from GHCR)\n└── .env.example                   Environment variable template\n```\n\n---\n\n## API\n\nInteractive docs available at [`/api/schema/swagger-ui/`](http://localhost:8000/api/schema/swagger-ui/) when running locally.\n\n```\n# Auth\nPOST    /api/auth/token/                  Login → { access, user } + httpOnly cookie\nPOST    /api/auth/token/refresh/          Rotate refresh token → { access }\nPOST    /api/auth/logout/                 Blacklist token + clear cookie\nGET     /api/auth/me/                     Current user\nPOST    /api/auth/register/               Create account (403 if disabled)\nPOST    /api/auth/google/                 Google ID-token login / register\nGET     /api/auth/profile/                User profile\nPUT     /api/auth/profile/                Update username / email\nPOST    /api/auth/change-password/        Change password + rotate JWT\n\n# Data (all owner-scoped, require Bearer token)\nCRUD    /api/assets/\nPOST    /api/assets/update-prices/        Enqueue → 202 { task_id }\nGET     /api/tasks/{task_id}/             Celery task status\nCRUD    /api/accounts/\nCRUD    /api/account-snapshots/\nPOST    /api/accounts/bulk-snapshot/\nGET/PUT /api/settings/\n\nCRUD    /api/transactions/\nCRUD    /api/dividends/\nCRUD    /api/interests/\nGET     /api/portfolio/\n\nGET     /api/reports/year-summary/\nGET     /api/reports/patrimonio-evolution/\nGET     /api/reports/rv-evolution/\nGET     /api/reports/monthly-savings/\nGET     /api/reports/annual-savings/\nGET     /api/reports/snapshot-status/\nGET     /api/reports/tax-declaration/?year=YYYY   Renta Web payload (Modo Renta)\n\nCRUD    /api/savings-goals/\nGET     /api/savings-goals/{id}/projection/   Goal progress projection\n\nCRUD    /api/properties/                      Real estate properties\nPOST    /api/properties/simulate/             Mortgage amortization simulation\nCRUD    /api/amortizations/                   Early amortization events (?property=uuid)\n\nGET     /api/storage-info/                    Database space usage\n\nGET     /api/export/transactions.csv\nGET     /api/export/dividends.csv\nGET     /api/export/interests.csv\nGET     /api/backup/export/\nPOST    /api/backup/import/\n\nGET     /api/health/                      Liveness probe\n```\n\n---\n\n## Environment Variables\n\n### Backend (`.env`)\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `DB_PASSWORD` | **yes** | — | PostgreSQL password |\n| `DJANGO_SECRET_KEY` | **yes** | — | Django secret key |\n| `DB_NAME` | | `fintrack` | Database name |\n| `DB_USER` | | `fintrack` | Database user |\n| `ALLOWED_HOSTS` | | `*` | Comma-separated allowed hostnames. `backend` is added automatically for internal Docker communication. |\n| `CORS_ALLOWED_ORIGINS` | | — | Comma-separated allowed origins (e.g., `https://fintrack.example.com`) |\n| `CSRF_TRUSTED_ORIGINS` | | same as CORS | Comma-separated CSRF trusted origins (e.g., `https://fintrack.example.com`) |\n| `REDIS_URL` | | `redis://redis:6379/0` | Celery broker + result backend |\n| `GOOGLE_CLIENT_ID` | | _(empty)_ | Google OAuth2 client ID |\n| `ALLOW_REGISTRATION` | | `true` | `false` = admin creates users via Django admin |\n| `DJANGO_SUPERUSER_USERNAME` | | `admin` | Initial superuser username |\n| `DJANGO_SUPERUSER_PASSWORD` | | `admin` | Initial superuser password |\n| `DJANGO_SUPERUSER_EMAIL` | | `admin@fintrack.local` | Initial superuser email |\n| `APP_PORT` | | `8080` | Host port for frontend (prod) |\n\n### Frontend\n\n| Variable | Default | Description |\n|---|---|---|\n| `NEXT_PUBLIC_API_URL` | `http://localhost:8000` | Public API URL |\n| `NEXT_PUBLIC_DEMO_MODE` | `false` | `true` shows \"Try Demo\" button (coexists with real auth) |\n| `DJANGO_INTERNAL_URL` | `http://backend:8000` | Internal Django URL (SSR) |\n\n---\n\n## Development\n\n### Common commands\n\n```bash\n# Start all services\ndocker compose up\n\n# Run backend tests\ndocker compose exec backend pytest\n\n# TypeScript check\ndocker compose exec frontend npx tsc --noEmit\n\n# Create migrations\ndocker compose exec backend python manage.py makemigrations \u003capp\u003e\n\n# Django shell\ndocker compose exec backend python manage.py shell\n\n# View Celery logs\ndocker compose logs -f celery_worker\n```\n\n### Celery tasks\n\n| Task | Schedule | Description |\n|---|---|---|\n| `snapshot_all_users_task` | Every 60s | Create portfolio snapshots when due |\n| `purge_old_snapshots_task` | Daily | Delete snapshots past retention period |\n| `update_prices_task` | On-demand | Fetch prices from Yahoo Finance |\n\n---\n\n## Production Deployment\n\n### Reverse proxy setup (Nginx Proxy Manager)\n\nPoint your domain to the frontend container:\n\n| Setting | Value |\n|---|---|\n| **Domain** | `fintrack.example.com` |\n| **Scheme** | `http` |\n| **Forward Hostname/IP** | server local IP (e.g., `192.168.1.171`) |\n| **Forward Port** | `8080` (or your `APP_PORT`) |\n| **Websockets Support** | enabled |\n| **SSL** | Let's Encrypt |\n\nThe Django admin panel is available at `http://\u003cserver-ip\u003e:8001/admin/` (add the IP to `ALLOWED_HOSTS`).\n\n### Startup sequence\n\nThe backend container runs automatically on each start:\n1. `migrate` — apply database migrations\n2. `collectstatic` — collect static files\n3. `createsuperuser` — create admin user (skips if already exists)\n4. `gunicorn` — start the application server\n\n### Updating\n\nAfter pushing to `main`, GitHub Actions rebuilds and publishes Docker images to GHCR.\n\n- **Portainer**: Stack → Editor → \"Update the stack\" with \"Re-pull image\" enabled\n- **CLI**: `docker compose -f docker-compose.prod.yml pull \u0026\u0026 docker compose -f docker-compose.prod.yml up -d`\n\n### Security settings (automatic when `DEBUG=False`)\n\n- `JWT_AUTH_COOKIE_SECURE = True`\n- `SECURE_HSTS_SECONDS = 31536000` (1 year)\n- `SECURE_SSL_REDIRECT = True`\n- `SESSION_COOKIE_SECURE = True`\n- `CSRF_COOKIE_SECURE = True`\n\n### Checklist\n\n- [ ] Set a strong `DJANGO_SECRET_KEY` (50+ random chars)\n- [ ] Set `DB_PASSWORD` to a secure value\n- [ ] Configure `ALLOWED_HOSTS` and `CORS_ALLOWED_ORIGINS`\n- [ ] Set `CSRF_TRUSTED_ORIGINS` for your domain (e.g., `https://fintrack.example.com`)\n- [ ] Configure `GOOGLE_CLIENT_ID` if using Google login\n- [ ] Set `ALLOW_REGISTRATION=false` if single-user\n- [ ] Set up SSL termination (NPM, Caddy, Traefik, or cloud LB)\n- [ ] Configure backup strategy for PostgreSQL\n- [ ] Change `DJANGO_SUPERUSER_PASSWORD`\n\n### Database Backups\n\n```bash\n# Manual backup (compressed)\ndocker compose -f docker-compose.prod.yml exec -T db pg_dump -U fintrack fintrack | gzip \u003e backups/fintrack_$(date +%Y%m%d).sql.gz\n\n# Restore from backup\ngunzip -c backups/fintrack_20260315.sql.gz | docker compose -f docker-compose.prod.yml exec -T db psql -U fintrack fintrack\n```\n\n**Automated daily backup with 30-day retention** — add to your server's crontab (`crontab -e`):\n\n```\n0 3 * * * cd /path/to/fintrack \u0026\u0026 mkdir -p backups \u0026\u0026 docker compose -f docker-compose.prod.yml exec -T db pg_dump -U fintrack fintrack | gzip \u003e backups/fintrack_$(date +\\%Y\\%m\\%d).sql.gz \u0026\u0026 find backups/ -name \"fintrack_*.sql.gz\" -mtime +30 -delete\n```\n\nThis runs at 3:00 AM daily, creates a compressed dump (~10-50 MB), and deletes backups older than 30 days.\n\n### Troubleshooting\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| `Bad Request (400)` | Domain/IP not in `ALLOWED_HOSTS` | Add it to `ALLOWED_HOSTS` env var |\n| `400` on login (POST) | `CSRF_TRUSTED_ORIGINS` not set | Set to `https://your-domain.com` |\n| `ECONNREFUSED` on login | Backend not ready | Wait — healthcheck has 30s start period |\n| `502 Bad Gateway` | Backend crashed | Check logs: `docker logs fintrack-backend-1` |\n| Admin login fails | Superuser not created | Verify `DJANGO_SUPERUSER_*` env vars are set |\n\n---\n\n## Documentation\n\n- [Development Guide](docs/DEVELOPMENT.md) — Setup, testing, linting, common tasks\n- [Architecture](docs/architecture.md) — C4 diagrams, system design, ADRs\n- [Security Policy](docs/SECURITY.md) — Vulnerability reporting, security measures, backups\n- [Contributing Guide](docs/CONTRIBUTING.md) — Branch naming, commit conventions, PR requirements\n- [Changelog](CHANGELOG.md) — Version history and notable changes\n\n## Contributing\n\nSee [CONTRIBUTING.md](docs/CONTRIBUTING.md) for the full guide. Quick summary:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feat/amazing-feature`)\n3. Write tests for your changes\n4. Ensure all CI checks pass (`lint`, `typecheck`, `tests`)\n5. Commit using [Conventional Commits](https://www.conventionalcommits.org/)\n6. Push and open a Pull Request\n\n### Conventions\n\n- **UI labels** use i18n (es, en, de, fr, it), **code** (variables, comments) in English\n- **Money**: Always `Decimal`, never `float`\n- **IDs**: UUID via `TimeStampedModel`\n- **Multi-tenancy**: Every model has `owner` FK, ViewSets use `OwnedByUserMixin`\n- **BFF Pattern**: Browser → Next.js → Django (browser never calls Django directly)\n\n---\n\n## License\n\n[MIT](LICENSE) — free to use, modify and self-host.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgonzalez8%2Ffintrack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgonzalez8%2Ffintrack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgonzalez8%2Ffintrack/lists"}