What this project is about

Hello, I’m Vlad, a software engineer with six years of professional experience.

I’m building ODInvestor to apply my skills, learn new technologies, and experiment with new ideas.

The project is built with Bun and Bun workspaces. It is divided into small, independent packages, including custom wrappers around community packages. I try to organize and reuse code carefully so the project remains simple, maintainable, and fast to develop.

Core & Analytics

Core is the API gateway — the single entry point everything else sits behind. It's an Elysia service with an auto-generated Swagger UI, and it owns everything user-facing: Google OAuth sign-in, sessions, notifications, settings, asset catalogs, and portfolio snapshots. It also issues scoped agent tokens, so external agents can talk to the API with narrowly limited permissions instead of full access.

Analytics is the number-crunching service behind it. I kept it deliberately lean — no auth, no third-party SDKs, just the database and the math. Core calls into Analytics constantly to build the figures I show on the dashboards and analytics pages, which keeps the heavy computation isolated from the request-handling layer.

Integrations

Integrations connects the platform to cryptocurrency exchanges. When a user links an account, this service handles the sync — pulling balances and activity through a shared exchange wrapper so that adding a new exchange means implementing one small adapter rather than touching the rest of the system.

Market

Market collects market data and exposes a clean API for the rest of the platform to read prices and tickers from. It caches through Redis so hot data is fast, and ships a refresh-tickers CLI for keeping the instrument catalog current. It's the single source of truth for "what is this asset worth right now."

OG

OG renders Open Graph preview images. It runs a headless browser (Puppeteer) to turn a page into a shareable image, then stores the result — so links to the project unfurl with a proper branded preview on social media and chat apps.

Reminder

Reminder is a small cron worker, not a long-running service. On a schedule it finds users whose monthly portfolio snapshot is due, builds a branded HTML email with their latest stats, sends it via Mailgun, marks them as reminded, and exits. Intentionally boring and stateless — it does one job and gets out of the way.

Site

Site is the project's static homepage — the landing page and the front door to everything else.

Blog

Blog is the configuration for a self-hosted Ghost instance, where I publish writing about the project and investing.

Uptime-Kuma

Uptime-Kuma is a self-hosted uptime monitor that watches all of the above and alerts me when something goes down.

The foundation underneath

None of these services is built from scratch. They share 13 internal workspace packages — thin, well-scoped wrappers I maintain around community libraries and my own utilities: databaseredisstorageloggerconfigexchangemarketnotificationsmailgunui, and more. Each service imports only what it needs. This is the part of the architecture I'm most deliberate about: it's what lets each microservice stay small and single-purpose while the tricky, shared logic lives in one tested place.

Plug your AI into your portfolio

The part of the project I'm proudest of: One Day Investor speaks LLM. Instead of clicking through thirty forms to recreate a portfolio I already have sitting in broker statements, I can hand the whole job to an AI.

You generate a token from your dashboard and paste something like this into ChatGPT, Claude, or Cursor:

"Here's my broker statement. Fetch odinvestor.net/agents.json with this token, create matching pockets, and add my holdings — confirm before any write."

The model reads the public manifest, discovers the endpoints on its own, and does the setup. From then on the same contract keeps working in plain language: "Add 2 BTC to my crypto pocket," "Take a snapshot for March," "Get my assets and tell me how diversified I am."

I built it around three rules:

  • The contract is public. /agents.json is an open manifest any agent can discover and read — no SDK to install, no plugin to approve, no vendor lock-in. If an LLM can make an HTTP request, it can use it.
  • You hold the keys. Tokens are shown once, hashed with SHA-256 before they ever touch the database, expire on a window you choose (1 hour to 30 days), and can be revoked instantly.
  • Nothing off-spec. An agent's reach is bounded by an allowlist enforced at the gateway. If an endpoint isn't in the manifest, the token literally can't call it — and management actions like minting new tokens are closed to agents entirely.

Underneath, it's a small, sharp piece of auth engineering: a reusable agent-scope plugin that every microservice mounts, cleanly separating "a human with a session cookie" from "an agent with a scoped bearer token" on every single request.

Deployment & CI/CD

The whole platform ships on a push-to-main pipeline. There's no manual step — I merge, and GitHub Actions does the rest.

The workflow SSHes into the production server and drives everything through Docker Compose:

  1. Pull the latest code.
  2. Build every service image from its own Dockerfile — core, analytics, market, reminder, og, site, frontend, blog.
  3. Migrate the database in a throwaway container before any new service starts, so the schema is always ahead of the code that depends on it.
  4. Roll the stack with compose up -d — Postgres, Redis, and MinIO (S3-compatible storage) come up healthy first, and each app waits on its dependencies via health checks before it starts.
  5. Smoke-test every service with a health-check request and report pass/fail in the deploy log.

I split the deployment into two tiers on purpose:

  • My own services are rebuilt from source on every push, so a merge always reflects the exact commit.
  • Third-party stacks (the Ghost blog, the Uptime-Kuma monitor) run as separate Compose projects with pinned images and isolated volumes — they get pulled, not rebuilt, and their data and secrets survive every redeploy untouched.

A few principles hold the whole thing together: no secrets in the repo — SSH keys live in GitHub Actions secrets and service credentials live in .env files on the server; everything internal is private — services bind to 127.0.0.1 and sit behind Nginx with Let's Encrypt TLS per subdomain; and the database is never left behind — migrations are gated ahead of the container swap on every single deploy.

It's intentionally lightweight — one VPS, one Compose file, no registry or orchestration overhead — but it gives me the things that actually matter: reproducible builds, safe migrations, and a one-merge path to production.

And I run this playground for 18$ on Hetzner, almost free and this project gave me large playground to test my ideas

Subscribe to One Day Investor

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe