{"id":50964791,"url":"https://github.com/anod/expenses-app","last_synced_at":"2026-06-18T18:32:43.091Z","repository":{"id":358133060,"uuid":"1240162205","full_name":"anod/expenses-app","owner":"anod","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-29T20:40:06.000Z","size":777,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T22:14:52.159Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/anod.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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-05-15T20:43:57.000Z","updated_at":"2026-05-29T20:40:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/anod/expenses-app","commit_stats":null,"previous_names":["anod/expenses-app"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/anod/expenses-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anod%2Fexpenses-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anod%2Fexpenses-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anod%2Fexpenses-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anod%2Fexpenses-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anod","download_url":"https://codeload.github.com/anod/expenses-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anod%2Fexpenses-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34503508,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-06-18T18:32:40.094Z","updated_at":"2026-06-18T18:32:43.085Z","avatar_url":"https://github.com/anod.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Expenses\n\nA small webapp around an existing OneDrive Excel workbook. Reads the workbook\nthrough Microsoft Graph, renders it as a table in the browser, and (in later\nphases) caches and edits it.\n\n## Stack\n\n- **packages/shared** — TypeScript contracts + workbook parser (vitest).\n- **apps/api** — Express + Pino API serving the parsed workbook snapshot.\n- **apps/web** — Angular 21 standalone PWA (signals, OnPush, `@switch`/`@for`).\n- **scripts/test-graph-connection.mjs** — MSAL device-code flow that dumps\n  the workbook used range to `dumps/dump-\u003ctimestamp\u003e.json`. Phase-0 tooling.\n\n## Layout\n\n```\n.\n├── apps/\n│   ├── api/      Express server reading the latest dump\n│   └── web/      Angular table view\n├── packages/\n│   └── shared/   Types + parser shared by api and web\n├── scripts/      Standalone Graph tooling (auth + dump)\n├── dumps/        Workbook dumps (gitignored)\n├── .env          Real config (gitignored)\n└── .env.example  Documented config template\n```\n\n## One-time setup\n\n1. **Register a Microsoft Entra app** — fastest path is\n   [`scripts/register-app.sh`](scripts/register-app.sh) (requires `az login`).\n   Manual portal steps are in [`SETUP_GRAPH.md`](SETUP_GRAPH.md).\n2. `cp .env.example .env` and fill in `MICROSOFT_CLIENT_ID` and\n   `ONEDRIVE_WORKBOOK_URL`.\n3. `npm install` at the repo root (npm workspaces will install all packages).\n\n## Dump the workbook (Phase 0)\n\n```bash\nnpm run dump\n```\n\nFirst run prints a device-code URL — sign in with the Microsoft account that\nowns the OneDrive file. The refresh token is cached in `.token-cache.json`\nso subsequent runs are non-interactive.\n\nThe dump lands in `dumps/dump-\u003cISO-timestamp\u003e.json`.\n\nA Dockerized variant is available too:\n\n```bash\ndocker compose run --rm graph-tester                     # connection test\ndocker compose run --rm graph-tester node scripts/test-graph-connection.mjs --dump\n```\n\n## Run the app (Phase 1 / Phase 4)\n\n```bash\nnpm run dev\n```\n\nThis spawns the API (`:4000`) and Angular dev server (`:4200`) concurrently.\nOpen \u003chttp://localhost:4200\u003e.\n\nThe API can run in either of two modes, controlled by `EXPENSES_SOURCE` in\nyour `.env`:\n\n| Mode  | Source                          | Sign-in required in browser? |\n| ----- | ------------------------------- | ---------------------------- |\n| `dump` | Latest file under `dumps/`      | No                           |\n| `graph` (default) | Live Microsoft Graph | Yes (Sign in with Microsoft) |\n\nToggle **Demo mode** from the in-app Settings page to swap the live data\nfor funny in-memory fake data (real DB and workbook are not touched).\nA **DEMO MODE** badge appears in the header while it's on. The toggle\nstate persists across restarts in `data/demo-mode.json`; demo edits\nthemselves live in memory only.\n\nIn **graph mode**, the API exposes `GET /api/config` (clientId, authority,\nscopes) which the Angular app fetches at bootstrap to initialize MSAL.js\n(auth-code + PKCE). Every `/api/*` request from the browser carries a\n`Authorization: Bearer \u003cuser token\u003e` header; the API forwards that token\nverbatim to Microsoft Graph. **No tokens are stored server-side.**\n\n### Sign-in flow\n\n1. Browser loads `/api/config` → initializes MSAL with the SPA `clientId`.\n2. User clicks **Sign in with Microsoft** (popup).\n3. MSAL caches the access + refresh tokens in `sessionStorage`.\n4. The HTTP interceptor adds `Authorization: Bearer …` to every `/api/*`\n   request. On a 401, it forces a silent refresh and retries once.\n\n### Endpoints\n\n| Endpoint | Auth | Notes |\n| --- | --- | --- |\n| `GET /healthz` | No | Liveness + current source |\n| `GET /api/config` | No | `{ source, auth: { clientId, authority, scopes } | null }` |\n| `GET /api/expenses` | Bearer (graph mode) | Parsed `WorkbookSnapshot` |\n| `GET /api/workbook/status` | Bearer (graph mode) | Workbook metadata only |\n\nRecurring templates support three schedule shapes:\n\n- **Monthly** — exact day-of-month\n- **Weekly** — exact day-of-week\n- **Monthly prediction** — one projected occurrence per month without an exact\n  business day\n\nWhen syncing back to Excel, only exact monthly day-of-month templates are\nround-trippable in the current workbook format. Weekly and monthly-prediction\ntemplates are excluded from the export sheets with a warning.\n\n## Other scripts\n\n| Command | Effect |\n| --- | --- |\n| `npm test` | Run all workspace tests (shared parser + API graph layer) |\n| `npm run build` | Build all workspaces |\n| `npm run test:connection` | Smoke-test the Microsoft Graph connection (no dump) |\n| `npm --workspace @expenses/api run backup:db` | Online SQLite backup (used by deploy cron) |\n\n## Deployment\n\nProduction deployment is covered in [`docs/deploy.md`](docs/deploy.md):\nDocker image, Proxmox LXC bootstrap, Tailscale-served TLS, and backups.\n\n## Roadmap\n\nThe full plan with all 8 phases is in\n[`LLM_IMPLEMENTATION_PLAN.md`](LLM_IMPLEMENTATION_PLAN.md). Phases 1 and 4\n(read-only table fed by either a dump or a live Graph fetch with browser\nsign-in) are complete; subsequent phases add a SQLite snapshot cache,\nwrite-through editing, and offline support.\n\n## Privacy\n\n`.env`, `.token-cache.json`, and `dumps/` are gitignored. Do not commit them.\nThe OneDrive sharing URL contains a capability token and is treated as a\nsecret throughout this repo.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanod%2Fexpenses-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanod%2Fexpenses-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanod%2Fexpenses-app/lists"}