Collaborative apartment comparison tool for small groups hunting for rentals together.
翻訳待ち
Loading the full README. The saved excerpt is shown below.
Compare apartments side-by-side without spreadsheets. Upload PDF listings, let AI pull out the numbers, rate places together with your household, see which one is the best deal at a glance — with the data encrypted in your browser before it ever reaches the server. Add a screenshot or short GIF of the apartments overview / compare page here once you have one — it sells the rest of the README. This is the part that shapes everything else in the cod [README 설치·사용 부분 발췌] Quickstart Open, sign in with the password you set, and choose an encryptioRead the full README ↗
| 🔐 Encrypted in your browser | Every apartment, rating, location and uploaded PDF is sealed with AES-256-GCM under a key only your household holds. The server stores ciphertext it cannot read. |
| 📄 PDF → structured data | Drop in a Swiss listing PDF, Gemini extracts rent, size, rooms, address, summary. Edits you make are preserved across re-parses. |
| ⭐ Per-user star ratings | Each member rates kitchen, balconies, location, floorplan and overall. Averages roll up automatically — computed in the browser, since the server cannot read the scores. |
| 📊 Comparison grid | Sortable side-by-side table — best price, biggest, shortest commute — with hide/show per apartment. |
| 🚲 Auto distance | Bike + transit minutes from each apartment to your "locations of interest" (work, schools, family). |
| 🗺️ Map view | A map page with every apartment and place drawn by hand on a clean street map (MapLibre + OpenFreeMap), and a "View on map" button on each apartment that opens it with that apartment highlighted. Self-hosters can use their own map file instead, so the map contacts no one. |
| 👥 Households + invitations | Invite people by email; each member gets their own account and their own copy of the household key. |
| ☁️ Cloud or self-hosted | One repo runs on Vercel + Turso + Vercel Blob, or fully self-hosted with SQLite + local disk and no third-party accounts at all. |
This is the part that shapes everything else in the codebase, so it is worth 60 seconds.
CryptoKeys. The server stores wrapped blobs and never sees the passphrase.apartments, ratings and locations each hold one envelope column of ciphertext. Names, addresses, rent, coordinates, distances, rating scores and comments all live inside it. Only what the server needs to scope and order rows stays in the clear: ids, household_id, timestamps, version, sort order./api/process/* acts as a blind proxy: plaintext goes in, a third-party result comes out, nothing is written to any table. The server sees one address, one URL or one PDF per call, in memory. We therefore do not describe Flatpare as zero-knowledge — that claim would be false.FLATPARE_ENCRYPTION=off for a self-hosted install that does not want key management. The choice is stamped into the database on first boot and cannot be changed afterwards without starting fresh.Accepted limits and the full threat model are in docs/security-notes.md.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 App Router (Server Components + Route Handlers) |
| Language | TypeScript 6, React 19 |
| Styling | Tailwind CSS 4, shadcn/ui |
| Database | Drizzle ORM over libSQL — Turso (cloud) or local SQLite |
| Crypto | WebCrypto (AES-256-GCM, RSA-OAEP-3072) + hash-wasm Argon2id |
| Auth | Auth.js v5 — Google / GitHub OAuth, or a shared password when self-hosting |
| File storage | Vercel Blob (cloud) or ./uploads/ (local) — ciphertext either way |
| AI | Vercel AI SDK + Google Gemini 2.5 Flash |
| Maps | Google Maps (Geocoding + Distance Matrix) with OpenRouteService bike-distance fallback; MapLibre over OpenFreeMap for rendering; self-hosters can use a Planetiler extract of OpenStreetMap instead |
| Payments | Stripe embedded Checkout — one-time, optional, off unless configured |
| Tests | Vitest 5 + React Testing Library, coverage floors enforced in CI |
| Deploy target | Vercel (preferred) or any Node 24+ host |
cp .env.example .env.local # set AUTH_SECRET, APP_PASSWORD and FLATPARE_MAPS at minimum
docker compose up -d
FLATPARE_MAPS is where the map's streets come from: openfreemap, or local with your own map file in ./maps (how). Open http://localhost:3002, sign in with the password you set, and choose an encryption passphrase when prompted. That is the whole local path — SQLite, filesystem uploads, manual entry, no cloud accounts and no billing.
Port note: the container listens on 3000;
docker-compose.ymlpublishes that to host port 3002. Override the host side withPORT=8080 docker compose up -d. The container side is fixed.
git clone https://github.com/brlauuu/flatpare.git
cd flatpare
npm install
cp .env.example .env.local # set AUTH_SECRET + APP_PASSWORD
npm run db:push # creates ./data/flatpare.db
npm run dev # http://localhost:3002
Drizzle migrations also run automatically at boot via src/instrumentation.ts, so a first-time npm run dev is usually enough on its own.
npm run devfollowsTURSO_DATABASE_URL. If.env.localsets it, the dev server writes to that cloud database, not to./data/flatpare.db. RunTURSO_DATABASE_URL= npm run devto force the local file. The app prints which one it picked at boot, and warns when a non-production build is pointed at the cloud.
This is the beginning of the README. Read the rest at the original. Read the full README ↗