A web app for lending teams to manage loans, investors, and cashflow in one place.
Built with SvelteKit 2 and Svelte 5 for day-to-day operations: recording Lot Title / OR/CR / Agent loans, tracking investor participation, interest periods, and collections. Hosted on Vercel, backed by Neon Postgres, with Google sign-in and optional Google Calendar sync for disbursements, due dates, and daily summaries.
Features · Screenshots · Tech Stack · Getting Started
- Create and manage Lot Title, OR/CR, and Agent loans
- Multi-investor allocation with per-investor principal, rates, and schedules
- Multiple interest periods, received payments, and overdue handling
- In-app calendar, table, and card views with filters and sorting
- Duplicate loans, PDF/CSV export, and manual Google Calendar sync from settings
- Investor directory with capital, returns, and active loan counts
- Investor portal: share loans via investor email (Google sign-in)
- Role-based access (
adminvsinvestor)
- Transaction ledger (collections, disbursements, returns)
- Dashboard with summary metrics, cashflow charts, and activity cards
- Past due, maturing, pending disbursement, and completed loan panels
- Privacy toggle to hide amounts and interest rates across the app (names, dates, and counts stay visible)
- Sign in with Google via Auth.js (
@auth/sveltekit). No passwords stored in the app - Sessions persisted with the Drizzle adapter (
users,accounts,sessions) - Role-based access:
admin(full workspace) andinvestor(shared loans & transactions) - Investor portal: link investors by email so they sign in with the same Google account and see assigned loans
Optional per-group Google Calendar sync via a Google Cloud service account (googleapis):
- Each loan group gets its own Google calendar (created by the service account)
- Loan create/update/delete and Settings Sync loan due dates enqueue group calendar jobs
- Manual full resync on the group hub (
SyncCalendarButton→POST /api/groups/[id]/calendar/sync) - Disbursement, due date, interest period, and daily summary events (Asia/Manila all-day semantics)
Requires GOOGLE_SERVICE_ACCOUNT_EMAIL and GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY in .env.example. Legacy workspace-wide calendar env (GOOGLE_CALENDAR_ID) is only for the one-off wipe script before decommissioning.
- JSON backup download and optional daily email backups (Resend + cron)
- Maintenance tools: sync due dates, fix received-payment totals
| Landing | Sign in |
|---|---|
![]() |
![]() |
Screenshots from the live app. Several views show the privacy toggle (eye icon) hiding names, amounts, dates, and rates.
| Dashboard | Loans (table) |
|---|---|
![]() |
![]() |
| Loans (calendar) | Loan detail |
|---|---|
![]() |
![]() |
| Create loan | Investors |
|---|---|
![]() |
![]() |
| Transactions |
|---|
![]() |
| Layer | Technology |
|---|---|
| Framework | SvelteKit 2 + Svelte 5 |
| UI | Tailwind CSS 4, shadcn-svelte |
| Authentication | Auth.js (@auth/sveltekit) + Google OAuth (src/lib/server/auth.ts) |
| Calendar | Google Calendar API via googleapis (src/lib/server/group-calendar.ts) |
| Database | Neon PostgreSQL + @neondatabase/serverless |
| Hosting | Vercel (@sveltejs/adapter-vercel) |
| ORM | Drizzle ORM |
| Forms | sveltekit-superforms + Zod |
| Charts | SVG donuts and CSS ranking tracks; ApexCharts only for cashflow. |
| PDF export | @react-pdf/renderer (server-only) |
| Email (optional) | Resend |
- Bun
- A Neon PostgreSQL database (or local Docker Postgres)
- Google Cloud project with:
- OAuth 2.0 credentials (Web application) for sign-in
- Calendar API enabled + service account (optional, for calendar sync)
git clone https://github.com/sprmke/pawn-tracker.git
cd pawn-tracker
bun installcp .env.example .env.localFill in at minimum:
| Variable | Description |
|---|---|
DATABASE_URL |
Neon connection string (?sslmode=require), or local Docker URL |
AUTH_SECRET |
Random secret: openssl rand -base64 32 |
AUTH_GOOGLE_ID |
Google OAuth client ID |
AUTH_GOOGLE_SECRET |
Google OAuth client secret |
Google Calendar (optional)
| Variable | Description |
|---|---|
GOOGLE_SERVICE_ACCOUNT_EMAIL |
Service account email from Google Cloud |
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY |
Service account private key (JSON key file) |
PUBLIC_APP_URL |
Canonical app origin (calendar links, signing). Default https://pawn-tracker.vercel.app |
Other optional: backups (RESEND_API_KEY, BACKUP_EMAIL, CRON_SECRET). See .env.example for the full list.
For local Docker Postgres:
bun run db:local:start
bun run db:local:pushFor Neon schema changes, add a file under db/migrations/ and merge to main (CD applies pending SQL before deploy). Locally: bun run db:migrate:pending. Prefer not using db:push against production.
If you upgrade an older database and see missing column errors, run db/migrations/0001_interest_incomplete_and_period_link.sql in the Neon SQL editor (see comments in that file for Postgres version notes).
bun run devOpen http://localhost:3200. Sign in with Google to access the dashboard. If the port is busy, run bun run dev:free-ports first (strictPort is on).
| Command | Description |
|---|---|
bun run dev |
Vite / SvelteKit dev server (:3200) |
bun run dev:free-ports |
Stop stale listeners on :3200 / :4174 |
bun run build |
Production build |
bun run preview |
Preview the production build |
bun run check |
svelte-check |
bun run test |
Vitest unit tests |
bun run db:local:start |
Start Docker Postgres |
bun run db:local:push |
Push schema to local Postgres only |
bun run db:generate |
Generate Drizzle migrations |
bun run db:migrate:pending |
Apply pending db/migrations/*.sql |
bun run deploy:prod |
Manual Vercel prod (prefer CD on main) |
bun run db:studio |
Open Drizzle Studio |
src/routes/ # SvelteKit pages and API (+server.ts)
src/lib/ # Domain logic, components, composables
src/lib/server/ # DB, auth, calendar, access-control, PDF
db/migrations/ # Shipped SQL patches (do not edit)
docs/ # Architecture, route guides, workflow
scripts/ # Backup, AI tooling, local DB








