Skip to content
Merged
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
10 changes: 2 additions & 8 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,8 @@
**/.cache
**/*.tsbuildinfo

**/.env
**/.env.local
**/.env.docker
**/.env.development.local
**/.env.test.local
**/.env.production.local
!**/.env.example
!**/.env.docker.example
**/.env*
!**/.env*.example

*.log
npm-debug.log*
Expand Down
12 changes: 8 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Docker Compose build-time variable substitution
# FinTrack root — Docker Compose build-arg template.
# Copy this file to .env and fill in your values.
# These variables are used by docker compose for build args (not runtime).
# Used by docker compose for build args at image build time (not runtime).

# Telegram Login Widget — baked into the Next.js web build
# Numeric bot id — digits before the ":" in the bot token (e.g. 123456789:ABC -> 123456789)
# ═════════════════════════════════════════════════════════════
# REQUIRED — web image won't build correctly without this
# ═════════════════════════════════════════════════════════════

# Telegram Login Widget — baked into the Next.js web build.
# Numeric bot id — digits before the ":" in the bot token (e.g. 123456789:ABC -> 123456789).
NEXT_PUBLIC_TELEGRAM_BOT_ID="your_numeric_bot_id"
10 changes: 0 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@ concurrency:
cancel-in-progress: true

jobs:
validate-env:
name: Validate ENV Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check if .env.example matches .env.docker.example
run: |
diff <(grep -v '^#' apps/api/.env.example | cut -d= -f1 | sort) <(grep -v '^#' apps/api/.env.docker.example | cut -d= -f1 | sort) || { echo "::error::API env examples are out of sync!"; exit 1; }
shell: bash

migration-check:
name: Migration Drift Check
runs-on: ubuntu-latest
Expand Down
32 changes: 15 additions & 17 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ cd FinTrack

# Use the dx CLI to create all necessary .env files from examples
bash dx setup
# → Edit .env for local run or .env.docker for Docker setup (DATABASE_URL host difference).
# → Edit each apps/*/.env. In Docker, compose overrides the PostgreSQL/Redis/Mongo hosts.
```

### 2. Docker (Recommended)
Expand Down Expand Up @@ -166,18 +166,16 @@ pnpm run dev

`bash dx setup` copies all example files automatically. For manual setup:

| File | Example | Notes |
| --------------------------- | ----------------------------------- | ------------------------------------------------------------ |
| `.env` (repo root) | `.env.example` | Docker Compose build args — `NEXT_PUBLIC_TELEGRAM_BOT_ID` |
| `apps/api/.env` | `apps/api/.env.example` | Local dev — fill in secrets |
| `apps/api/.env.docker` | `apps/api/.env.docker.example` | Docker dev — DB host is `postgres` |
| `apps/api/.env.test` | `apps/api/.env.test.example` | Local tests — points to `fintrack_test` |
| `apps/api/.env.test.docker` | `apps/api/.env.test.docker.example` | Docker tests — DB host is `postgres` |
| `apps/web/.env` | `apps/web/.env.example` | Set `NEXT_PUBLIC_API_URL`, `NEXTAUTH_SECRET`, Google OAuth |
| `apps/bot/.env` | `apps/bot/.env.example` | Local dev — set `TELEGRAM_BOT_TOKEN`, `API_URL`, `REDIS_URL` |
| `apps/bot/.env.docker` | `apps/bot/.env.docker.example` | Docker dev — same vars, host points to Docker service |
| File | Example | Notes |
| -------------------- | ---------------------------- | ------------------------------------------------------------------------- |
| `.env` (repo root) | `.env.example` | Docker Compose build args — `NEXT_PUBLIC_TELEGRAM_BOT_ID` |
| `apps/api/.env` | `apps/api/.env.example` | Dev + Docker — secrets; Docker hosts injected by compose |
| `apps/api/.env.test` | `apps/api/.env.test.example` | Tests — `fintrack_test`; Docker hosts rewritten by `scripts/test-env.cjs` |
| `apps/web/.env` | `apps/web/.env.example` | Set `NEXT_PUBLIC_API_URL`, `NEXTAUTH_SECRET`, Google OAuth |
| `apps/bot/.env` | `apps/bot/.env.example` | Dev + Docker — `TELEGRAM_BOT_TOKEN`; Docker hosts injected by compose |

Each example file is annotated — read it for variable descriptions and required values.
Each example file is split into REQUIRED (app won't boot without these) and
OPTIONAL (safe defaults + feature toggles) blocks — read it for per-variable details.

---

Expand Down Expand Up @@ -257,7 +255,8 @@ The test database (`fintrack_test`) is separate from the dev DB and is used excl
cd apps/api

cp .env.example .env
# fill in DATABASE_URL, ACCESS_TOKEN_SECRET, GROQ_API_KEY_1, STRIPE_*, GOOGLE_CLIENT_ID, TELEGRAM_BOT_TOKEN ...
# fill in the REQUIRED block (DATABASE_URL, ACCESS_TOKEN_SECRET, GOOGLE_CLIENT_ID, TELEGRAM_BOT_TOKEN ...);
# OPTIONAL vars (GROQ_API_KEY_1, STRIPE_*, ...) gate features and can stay empty

pnpm run prisma:migrate:dev # apply migrations
pnpm run prisma:seed # optional seed data
Expand Down Expand Up @@ -343,10 +342,9 @@ Every tier has a `:dx` variant that targets the Docker environment (e.g. `test:l

### Environment Files

| File | Used by | DB host |
| --------------------------- | ---------------------- | --------------------------- |
| `apps/api/.env.test` | local `test:*` scripts | `localhost` |
| `apps/api/.env.test.docker` | `test:*:dx` scripts | `postgres` (Docker service) |
| File | Used by | DB host |
| -------------------- | ------------------------------- | ------------------------------------------------------------------------------ |
| `apps/api/.env.test` | local + Docker `test:*` scripts | `localhost`; `:dx` scripts rewrite it to `postgres` via `scripts/test-env.cjs` |

### Running Tests

Expand Down
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ The database uses **PostgreSQL 15** managed via Prisma migrations. Key models:
| `donation` | `/donations` | Create Stripe Checkout session, webhook handler, leaderboard |
| `admin` | `/admin` | User list, role update, session revocation, error log management, stats |

Interactive Swagger docs are available at `/api-docs` (`ENABLE_SWAGGER_IN_PROD=true` or in dev mode).
Interactive Swagger docs are served at `/api-docs` automatically in dev and test; in production they are exposed only when `ENABLE_SWAGGER_IN_PROD=true`.

### Security

Expand Down Expand Up @@ -278,17 +278,16 @@ Database migrations are applied automatically from GitHub Actions on pushes to `

GitHub Actions runs the following checks on every pull request and push to `master`/`main`:

1. **ENV Docs Validation** — verifies `apps/api/.env.example` and `apps/api/.env.docker.example` stay in sync.
2. **Migration Drift** — non-blocking check that `schema.prisma` matches migrations (advisory).
3. **Format & Lint** — `prettier`, `eslint`, and ToC freshness check.
4. **Type check** — `tsc --noEmit` across the monorepo.
5. **Security Audit** — `pnpm audit` and dependency review.
6. **Tests** — Jest + Supertest API tests and Vitest web tests against a real PostgreSQL container.
7. **Release Gate** — on push to `master`/`main`, `gate.yml` listens for CI completion and dispatches `release.yml` only when CI succeeds; if CI fails, dispatch is skipped and a failure is logged.
8. **Release Workflow (`release.yml`)** — also triggers directly on push/PR to `master`/`main`; builds and (on push) publishes images to **GHCR**.
9. **Security Scanning** — **Trivy** scans every Docker image for vulnerabilities (CRITICAL, HIGH).
10. **CodeQL Analysis** — static analysis of JavaScript/TypeScript on every PR and push, plus a weekly scheduled scan.
11. **Prisma Auto-Migrate (`master`)** — applies `apps/api` migrations to Supabase after schema/migration changes.
1. **Migration Drift** — non-blocking check that `schema.prisma` matches migrations (advisory).
2. **Format & Lint** — `prettier`, `eslint`, and ToC freshness check.
3. **Type check** — `tsc --noEmit` across the monorepo.
4. **Security Audit** — `pnpm audit` and dependency review.
5. **Tests** — Jest + Supertest API tests and Vitest web tests against a real PostgreSQL container.
6. **Release Gate** — on push to `master`/`main`, `gate.yml` listens for CI completion and dispatches `release.yml` only when CI succeeds; if CI fails, dispatch is skipped and a failure is logged.
7. **Release Workflow (`release.yml`)** — also triggers directly on push/PR to `master`/`main`; builds and (on push) publishes images to **GHCR**.
8. **Security Scanning** — **Trivy** scans every Docker image for vulnerabilities (CRITICAL, HIGH).
9. **CodeQL Analysis** — static analysis of JavaScript/TypeScript on every PR and push, plus a weekly scheduled scan.
10. **Prisma Auto-Migrate (`master`)** — applies `apps/api` migrations to Supabase after schema/migration changes.

### Auto Migration Secrets (GitHub)

Expand Down
56 changes: 0 additions & 56 deletions apps/api/.env.docker.example

This file was deleted.

87 changes: 51 additions & 36 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
@@ -1,56 +1,71 @@
# Environment
NODE_ENV="development"
ENABLE_SWAGGER_IN_PROD="false"
# FinTrack API — environment template.
# Set everything under REQUIRED; OPTIONAL has safe defaults and feature toggles.

# App setup
HOST="localhost"
PORT="8000"
SWAGGER_SERVER_URL="http://localhost:8000/api"
CORS_ORIGINS="http://localhost,http://localhost:5173,http://localhost:8080,http://localhost:8000,http://127.0.0.1:5173"
FRONTEND_URL="http://localhost:5173"
# ═════════════════════════════════════════════════════════════
# REQUIRED — app refuses to boot without these
# ═════════════════════════════════════════════════════════════

# Local PostgreSQL / Prisma (development DB only)
# Databases — hosts target local (non-Docker) runs. In Docker they are
# rewritten to the service names (postgres/redis/mongo) by compose for the
# running app, and by scripts/test-env.cjs for tests.
DATABASE_URL="postgresql://fintrack:fintrack@localhost:5432/fintrack?schema=public"
DIRECT_URL="postgresql://fintrack:fintrack@localhost:5432/fintrack?schema=public"

# Redis
REDIS_URL="redis://localhost:6379"

# JWT/access token
ACCESS_TOKEN_SECRET="your_jwt_access_token_secret_here"
# Secrets & tokens
ACCESS_TOKEN_SECRET="your_access_token_secret_here"
CSRF_SECRET="your_csrf_secret_here"
API_KEY_ENCRYPTION_SECRET="your_api_key_encryption_secret_here" # any non-empty value (hashed to 32 bytes)

# Google OAuth verification (must match Google Cloud OAuth client)
GOOGLE_CLIENT_ID="your_google_client_id.apps.googleusercontent.com"
GOOGLE_OAUTH_VERIFY_MODE="verifyIdToken" # verifyIdToken | tokeninfo
# Auth providers
GOOGLE_CLIENT_ID="your_google_client_id.apps.googleusercontent.com" # must match Google Cloud OAuth client
TELEGRAM_BOT_TOKEN="123456789:your_telegram_bot_token" # Telegram Login Widget verification

# ═════════════════════════════════════════════════════════════
# OPTIONAL — safe defaults; features stay off until configured
# ═════════════════════════════════════════════════════════════

# Runtime
NODE_ENV="development" # default: development
ENABLE_SWAGGER_IN_PROD="false" # default: false

# Telegram Login Widget verification
TELEGRAM_BOT_TOKEN="123456789:your_telegram_bot_token"
# Server (localhost defaults applied when unset)
HOST="localhost"
PORT="8000"
SWAGGER_SERVER_URL="http://localhost:8000/api"
CORS_ORIGINS="http://localhost,http://localhost:5173,http://localhost:8080,http://localhost:8000,http://127.0.0.1:5173"
FRONTEND_URL="http://localhost:5173"

# Optional: link the seeded admin user to your real Telegram ID so the bot
# Auth tuning
GOOGLE_OAUTH_VERIFY_MODE="verifyIdToken" # verifyIdToken | tokeninfo
# Link the seeded admin user to your real Telegram ID so the bot
# authenticates as admin and shows the rich seeded transactions.
# SEED_TELEGRAM_LINK_ID="123456789"

# AI apis
GROQ_API_KEY_1=your_groq_api_key
API_KEY_ENCRYPTION_SECRET=your-32-char-secret-here # 32+ symb

# Stripe donation
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_DONATION_PRICE_ID=price_xxx # optional if amount/currency is used
STRIPE_DONATION_AMOUNT=300 # in minor units, e.g. 300 = $3.00
STRIPE_DONATION_CURRENCY=usd
STRIPE_DONATION_SUCCESS_URL=http://localhost:5173/FinTrack/donation?status=success
STRIPE_DONATION_CANCEL_URL=http://localhost:5173/FinTrack/donation?status=cancel
STRIPE_DONATION_DURATION_DAYS=0 # 0 or empty = permanent donor

# SMTP — email verification (Gmail example)
# Feature · Audit log (MongoDB) — leave unset to disable.
# MONGO_URL="mongodb://localhost:27017/fintrack"

# Feature · AI categorization (Groq) — without a key it is skipped (warns).
# Add more keys as GROQ_API_KEY_2, _3, …
GROQ_API_KEY_1="your_groq_api_key"

# Feature · Donations (Stripe) — leave empty to disable the donation flow.
STRIPE_SECRET_KEY="sk_test_xxx"
STRIPE_WEBHOOK_SECRET="whsec_xxx"
STRIPE_DONATION_PRICE_ID="price_xxx" # if unset, the amount/currency path is used
STRIPE_DONATION_AMOUNT="300" # minor units, e.g. 300 = $3.00
STRIPE_DONATION_CURRENCY="usd"
STRIPE_DONATION_SUCCESS_URL="http://localhost:5173/donation?status=success"
STRIPE_DONATION_CANCEL_URL="http://localhost:5173/donation?status=cancel"
STRIPE_DONATION_DURATION_DAYS="0" # 0 or empty = permanent donor

# Feature · Email verification (SMTP) — empty host/user/pass makes the mailer
# skip real sending (see src/utils/mailer.ts).
# For Gmail: enable 2FA, create App Password at myaccount.google.com/apppasswords
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_SECURE="false" # true only for port 465
SMTP_USER="your@gmail.com"
SMTP_PASS="your_app_password" #https://myaccount.google.com/apppasswords
SMTP_PASS="your_app_password" # https://myaccount.google.com/apppasswords
SMTP_FROM="FinTrack <your@gmail.com>"
EMAIL_VERIFICATION_BASE_URL="http://localhost:8000/api"
51 changes: 0 additions & 51 deletions apps/api/.env.test.docker.example

This file was deleted.

Loading
Loading