An opinionated personal investing tracker
Built with Next.js App Router, tRPC v11, Prisma/PostgreSQL, shadcn/ui, and InfluxDB for fast timeseries. Auth is powered by Better Auth (Prisma adapter) with email/password, magic links, 2FA, and Discord OAuth.
Getting Started โข Features โข Documentation โข Contributing
- ๐ Watchlist with historical OHLCV from InfluxDB (AAPL, MSFT, etc.)
- ๐ Date range filtering with customizable presets and max date constraints
- ๐ Interactive charts with Recharts integration
- ๐ฐ Corporate events: dividends, splits, capital gains
- ๐ Transactions with CSV import/export, duplicate detection, and FX-aware currencies
- ๐ Sortable columns with visual indicators
- ๐ Advanced filtering and search
- ๐ฏ Portfolio analytics: structure and performance calculations (TWR/MWR via tRPC)
- ๐ช Goals tracking: simple personal financial goals model
- ๐จ Modern UI: theming, toasts, and shadcn/ui + Recharts
- โก TanStack Table v8 for complex data tables
- ๐ Debounced search inputs (300ms) for better UX
- ๐ Skeleton loading states for professional loading experience
- ๐ฏ Active navigation indicators in sidebar
- ๐ Auto-sync: Yahoo Finance ingestion job for OHLCV and events; FX rates via Alpha Vantage
- ๐ Admin interface: comprehensive user management and audit logging
- ๐ฅ User management with sorting, filtering, and role-based permissions
- ๐ Audit logs with date range filtering and action tracking
- ๐ Statistics dashboard with user metrics
- ๐ Debounced search across users and logs
- ๐ API Keys: programmatic access with granular permissions
- ๐ฏ 8 permission scopes (account, admin, apiKeys, fx, goals, portfolio, transactions, watchlist)
- ๐ 4 built-in templates (read-only, full-access, portfolio-manager, custom)
- ๐ฆ Rate limiting and expiration controls
- ๐ SHA-256 hashed keys with secure generation
- ๐ Usage tracking and management UI
| Layer | Technology |
|---|---|
| Frontend | Next.js 15 (App Router) โข React 19 |
| API | tRPC v11 + React Query (RSC + CSR hydration) |
| Database | Prisma + PostgreSQL |
| Auth | Better Auth (email/password, magic link, 2FA, Discord) |
| UI | shadcn/ui + TailwindCSS 4 โข TanStack Table v8 |
| Charts | Recharts |
| Timeseries | InfluxDB 2.x (daily_bars + event measurements) |
graph TB
A[Client: React 19 + Next.js 15] --> B[tRPC v11]
B --> C[API Routers]
C --> D[Prisma ORM]
C --> E[InfluxDB Client]
D --> F[(PostgreSQL)]
E --> G[(InfluxDB 2.x)]
C --> H[Better Auth]
H --> F
Key components:
- ๐ App Router under
src/app/*; dashboard shell insrc/app/(dashboard)/layout.tsx - ๐ tRPC routers in
src/server/api/routers/*, composed insrc/server/api/root.ts - ๐ Context includes
db(Prisma) andsessionfrom Better Auth insrc/server/api/trpc.ts - โก Influx helpers in
src/server/influx.ts - ๐ฅ Yahoo ingestion in
src/server/jobs/ingest-yahoo.tswith helpers inyahoo-lib.ts - ๐ช Client hooks in
src/trpc/react.tsx; RSC callers viasrc/trpc/server.ts
Example API usage:
// Server (RSC)
import { api } from "@/trpc/server";
const me = await api.account.getMe.query();
// Client (CSR)
import { api } from "@/trpc/react";
const { data } = api.watchlist.list.useQuery();- โ Bun 1.0+
- โ Node 20+ (optional, Bun runs everything)
- โ
A PostgreSQL 16 database (use
./start-database.sh) - โ An InfluxDB 2.x instance (local or remote)
1๏ธโฃ Install dependencies
bun install2๏ธโฃ Create and fill .env
See the Environment variables section below. For a smoke test you can set placeholders and use SKIP_ENV_VALIDATION=1 while you iterate.
3๏ธโฃ Start Postgres (dev helper)
./start-database.sh4๏ธโฃ Generate and apply Prisma schema
bun run db:generate5๏ธโฃ Start the dev server
bun run dev๐ Open http://localhost:3000
Validated in src/env.js via @t3-oss/env-nextjs. Server-side vars are required unless noted.
๐ Core application
DATABASE_URL: Postgres connection URL (required)BETTER_AUTH_SECRET: Secret for Better Auth JWT/cookies (required in production)BETTER_AUTH_URL: Public base URL of the app (default: http://localhost:3000)PASSWORD_PEPPER: Extra pepper for hashing local passwords (required)EMAIL_SERVER: Nodemailer connection string (e.g. SMTP URI) (required)EMAIL_FROM: From address for transactional emails (required)NEXT_PUBLIC_SITE_URL: Public site URL exposed to client (default: http://localhost:3000)
๐ Auth providers
AUTH_DISCORD_ID,AUTH_DISCORD_SECRET: Discord OAuth credentials (required if enabling Discord)
๐พ Data backends
INFLUXDB_URL(default http://localhost:8086)INFLUXDB_ORGINFLUXDB_BUCKETINFLUXDB_TOKENDATABASE_URL(Postgres, repeated for clarity)
๐ External APIs
FINNHUB_API_URL(default https://finnhub.io/api/v1)FINNHUB_API_KEY(required for search and symbol validation)ALPHAVANTAGE_API_URL(default https://www.alphavantage.co/query)ALPHAVANTAGE_API_KEY(required for FX ingestion)YAHOO_CHART_API_URL(default https://query2.finance.yahoo.com/v8/finance/chart)POLYGON_API_URL,POLYGON_API_KEY(present in schema; not currently required by code paths)
โ๏ธ Optional/infra
CLOUDFLARE_*(R2 image storage wiring present; optional)NEXT_PUBLIC_*for Ads/Analytics (Umami/GA/AdSense) are optional and stubbed in E2E tests
๐ก Tip: during early setup, export
SKIP_ENV_VALIDATION=1to bypass strict checks until you've filled everything in.
| Command | Description |
|---|---|
bun run db:generate |
Generate/apply in dev (migrate dev) |
bun run db:migrate |
Deploy migrations |
bun run db:push |
Push schema (no migrations) |
bun run db:studio |
Prisma Studio (defaults to port 5000) |
Schema: prisma/schema.prisma
Relevant models: User, Account, Session, WatchlistItem, Transaction, FxRate, Goal, ApiKey and Better Auth support tables TwoFactor, Verification, VerificationToken.
| Measurement | Fields | Tags | Purpose |
|---|---|---|---|
daily_bars |
open, high, low, close, volume | symbol | OHLCV data |
dividends |
amount | symbol | Dividend events |
splits |
numerator, denominator, ratio | symbol | Stock split events |
capital_gains |
amount | symbol | Capital gain distributions |
Helper code: src/server/influx.ts
bun run ingest:yahooWhat it does:
- โ
Reads distinct symbols from your
WatchlistItems - โ Fetches full-range daily bars plus dividends, splits, capital gains
- โ Writes to Influx in batches with retries and gentle pacing (~2s/request)
- โ Also updates watchlist currency when available
- โ Adding a symbol to your watchlist triggers a background ingest for that symbol
bun run ingest:fxWhat it does:
- โ
Fetches pivoted rates through USD and upserts cross rates into
FxRate
E2E tests with Playwright:
# Install browsers once
bun run test:e2e:install
# Run headless
bun run test:e2e
# Run headed or with the UI
bun run test:e2e:headed
bun run test:e2e:uiConfig: playwright.config.ts (uses a built-in dev server unless PW_SKIP_WEBSERVER=1).
Comprehensive Postman collections are available in the postman/ directory for testing all API endpoints.
Collections include:
- ๐ Authentication - Sign up, login, password reset (Better Auth)
- ๐ค Account - Profile management, 2FA, email changes, OAuth
- ๐ Watchlist - Symbol management, price history, corporate events
- ๐ฐ Transactions - CRUD operations, CSV import/export, filtering
- ๐ Portfolio - Structure and performance analytics (TWR/MWR)
- ๐ฏ Goals - Financial goals tracking
- ๐ API Keys - Programmatic access management
- ๐ฅ Admin - User management, audit logs (admin only)
- ๐ฑ Currency & Theme - User preferences
- ๐ FX - Foreign exchange rates
Quick start:
# Import into Postman
# 1. Open Postman
# 2. Import postman/Invest-igator-API.postman_collection.json
# 3. Import postman/Invest-igator.postman_environment.json
# 4. Configure environment variables (baseUrl, etc.)
# 5. Sign in via Better Auth endpoints๐ See postman/README.md for detailed usage instructions
All 59 endpoints are documented with:
- โ Request/response examples
- โ Input validation details
- โ Authentication requirements
- โ Environment variables for easy configuration
bun run check # Biome
bun run typecheck # TypeScriptdocker build -t invest-igator:local .docker run --rm -p 3000:3000 \
-e DATABASE_URL=postgresql://user:pass@host:5432/db \
-e BETTER_AUTH_SECRET=change-me \
-e PASSWORD_PEPPER=change-me \
-e EMAIL_SERVER=smtp://user:pass@mail:587 \
-e [email protected] \
-e FINNHUB_API_KEY=... \
-e ALPHAVANTAGE_API_KEY=... \
-e INFLUXDB_URL=http://influx:8086 \
-e INFLUXDB_ORG=... \
-e INFLUXDB_BUCKET=... \
-e INFLUXDB_TOKEN=... \
invest-igator:localThis repo includes a Compose file that runs:
invest-igator: the app container (published on port 3311)db: internal Postgres 16scheduler: Ofelia to run ingestion jobs inside the app container on a cron
Quick start:
cp .env.example .env # if you have one; otherwise create .env from the vars above
# Fill DATABASE_URL, BETTER_AUTH_SECRET, PASSWORD_PEPPER, INFLUXDB_*, FINNHUB/ALPHAVANTAGE, EMAIL_*
docker compose up -dNotes:
โ ๏ธ Compose expects you to pointINFLUXDB_URLto an existing Influx instance (not included in the stack)- โฐ Cron labels run
ingest-yahoodaily at 02:15 UTC andingest-fxat 06:00/18:00 UTC - ๐ Migrations run automatically on container start
Stop and remove:
docker compose down -v.
โโโ prisma/
โ โโโ schema.prisma # ๐๏ธ Relational models (User, WatchlistItem, Transaction, etc.)
โ โโโ migrations/ # ๐ฆ Schema migrations
โโโ src/
โ โโโ server/
โ โ โโโ api/
โ โ โ โโโ root.ts # ๐ tRPC router composition
โ โ โ โโโ trpc.ts # โ๏ธ Context, middleware, procedures
โ โ โ โโโ routers/ # ๐งฉ Feature routers (watchlist, transactions, admin, etc.)
โ โ โโโ jobs/
โ โ โ โโโ ingest-yahoo.ts # ๐ Yahoo Finance ingestion job
โ โ โ โโโ ingest-fx.ts # ๐ฑ FX rates ingestion job
โ โ โ โโโ yahoo-lib.ts # ๐ ๏ธ Yahoo data helpers
โ โ โโโ auth/
โ โ โ โโโ config.ts # ๐ Better Auth configuration
โ โ โโโ db.ts # ๐๏ธ Prisma client singleton
โ โ โโโ influx.ts # ๐ InfluxDB client & helpers
โ โ โโโ fx.ts # ๐ฐ FX rate conversion utilities
โ โ โโโ r2.ts # โ๏ธ Cloudflare R2 storage client
โ โโโ app/
โ โ โโโ (dashboard)/ # ๐ Protected dashboard routes
โ โ โ โโโ watchlist/ # ๐ Watchlist with charts and date filtering
โ โ โ โโโ portfolio/ # ๐ผ Portfolio analytics
โ โ โ โโโ transactions/ # ๐ Transaction management with sorting
โ โ โ โโโ account/ # ๐ค Account settings with API Keys tab
โ โ โ โโโ admin/ # ๐ Admin section
โ โ โ โโโ users/ # ๐ฅ User management page
โ โ โ โโโ audit-logs/ # ๐ Audit logs page
โ โ โโโ (auth)/ # ๐ Auth routes (login, signup, verify-request)
โ โ โโโ api/ # ๐ API endpoints (tRPC, auth, email verification)
โ โ โโโ layout.tsx # ๐จ Root layout with providers
โ โโโ components/
โ โ โโโ ui/ # ๐งฑ shadcn/ui primitives (incl. DateRangePicker, Skeleton)
โ โ โโโ ads/ # ๐ข AdSense integration
โ โ โโโ consent/ # โ
Cookie consent provider
โ โ โโโ theme/ # ๐ Theme provider
โ โโโ hooks/
โ โ โโโ use-debounce.ts # โฑ๏ธ Debounce hook (300ms)
โ โ โโโ use-currency.ts # ๐ฑ Currency utilities
โ โโโ trpc/
โ โ โโโ react.tsx # โ๏ธ Client-side tRPC hooks
โ โ โโโ server.ts # ๐ฅ๏ธ Server-side tRPC helpers
โ โ โโโ query-client.ts # ๐ก React Query configuration
โ โโโ lib/
โ โ โโโ auth.ts # ๐ Better Auth instance
โ โ โโโ api-key-permissions.ts # ๐ API key permission framework
โ โ โโโ api-key-utils.ts # ๐ง API key generation & validation
โ โ โโโ utils.ts # ๐ง Utility functions (cn, etc.)
โ โโโ env.js # โ๏ธ Environment validation (@t3-oss/env-nextjs)
โโโ tests/
โ โโโ e2e/ # ๐งช Playwright E2E tests
โโโ docker/
โ โโโ entrypoint.sh # ๐ณ Container startup script (migrations, server)
โโโ Dockerfile # ๐ฆ Multi-stage build (deps, builder, runner)
โโโ docker-compose.yml # ๐ Compose stack (app, db, scheduler)
โโโ start-database.sh # ๐๏ธ Dev Postgres script
Quick pointers:
- Dashboard shell:
src/app/(dashboard)/layout.tsx - Sidebar with active navigation:
src/app/(dashboard)/_components/app-sidebar.tsx - tRPC glue:
src/server/api/trpc.ts,src/trpc/react.tsx,src/trpc/server.ts - API key system:
- Permission framework:
src/lib/api-key-permissions.ts - Key utilities:
src/lib/api-key-utils.ts - Router:
src/server/api/routers/api-keys.ts - Middleware:
src/server/api/middleware/with-api-key.ts - UI components:
src/app/(dashboard)/account/_components/api-keys-*.tsx
- Permission framework:
- Admin routers with sorting:
src/server/api/routers/admin.ts - Table components (reference):
src/app/(dashboard)/admin/_components/user-management-table.tsx,audit-logs-table.tsx - Influx helpers:
src/server/influx.ts - Ingestion:
src/server/jobs/ingest-yahoo.ts - Example router:
src/server/api/routers/watchlist.ts - Reusable components:
src/components/ui/date-range-picker.tsx,src/components/ui/skeleton.tsx - Debounce hook:
src/hooks/use-debounce.ts
Invest-igator supports programmatic API access via API keys with granular permission control.
- ๐ฏ 8 permission scopes: account, admin, apiKeys, fx, goals, portfolio, transactions, watchlist
- ๐ 4 built-in templates:
read-only: Read access to all non-admin endpointsfull-access: Read + write + delete for all user resourcesportfolio-manager: Portfolio, transactions, and goals managementcustom: Build your own permission set
- ๐ฆ Rate limiting: Configure per-key request limits with automatic refills
- โฐ Expiration control: Set expiry dates or create permanent keys
- ๐ Secure by design: SHA-256 hashed keys, cryptographically secure generation
- ๐ Management UI: Create, view, and revoke keys from your account page
- Create an API key: Go to Account โ API Keys tab and click "Create API Key"
- Select permissions: Choose a template or create custom permissions
- Copy your key: Save it securely - it's only shown once!
- Make requests: Include
x-api-keyheader in your API calls
# Get your user profile
curl https://your-app.com/api/trpc/account.getMe \
-H "x-api-key: your_api_key_here"
# Get FX rates matrix
curl https://your-app.com/api/trpc/fx.matrix \
-H "x-api-key: your_api_key_here"
# List watchlist items
curl https://your-app.com/api/trpc/watchlist.list \
-H "x-api-key: your_api_key_here"- ๐ API Key Permissions: Detailed permission system documentation
- ๐ API Key Usage Guide: Examples in cURL, JavaScript/TypeScript, and Python
| Issue | Solution |
|---|---|
| โ Env validation failing at boot | Set SKIP_ENV_VALIDATION=1 temporarily and fill missing vars (see src/env.js) |
| โ Prisma migrate errors | Ensure Postgres is reachable; try prisma migrate reset in dev |
| โ Influx writes fail | Verify INFLUXDB_URL/ORG/BUCKET/TOKEN and token has write permissions |
| โ Emails not sending | Verify EMAIL_SERVER URI and that your provider allows SMTP from containers |
| โ 401s in tRPC | Confirm cookies are set and BETTER_AUTH_URL matches your external origin |
PRs are welcome! Before submitting:
โ
Run bun run check (Biome linting)
โ
Run bun run typecheck (TypeScript)
โ
Include/update E2E tests where relevant
Quick demo dataset:
Add a few symbols to your watchlist and run the Yahoo ingest job; the watchlist add flow also triggers a background ingest for that symbol.