docs: overhaul README and mark project proprietary

- Rewrite README: accurate current feature set (public API, webhooks/Zapier,
  PayPal, accounting sync, e-sign, team, branding, inspections, maps, ~30 tables,
  4 cron jobs), emoji section headers, clearer setup and security sections.
- Remove the inline env-variable example block; point to .env.example / DIGITALOCEAN.md.
- Fix stale "local disk" storage references to DigitalOcean Spaces.
- License: change MIT -> Proprietary; add LICENSE file and package.json "UNLICENSED".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-07-02 13:51:59 -04:00
co-authored by Claude Opus 4.8
parent c9968531e4
commit 917a06ee85
3 changed files with 156 additions and 136 deletions
+22
View File
@@ -0,0 +1,22 @@
PROPRIETARY SOFTWARE LICENSE
Copyright (c) 2026 Property Management Network. All rights reserved.
This software and its source code (the "Software") are proprietary and
confidential. The Software is licensed, not sold.
No permission is granted to any person or entity to use, copy, reproduce,
modify, merge, publish, distribute, sublicense, sell, or create derivative
works of the Software, in whole or in part, by any means, without the prior
express written consent of the copyright holder.
Unauthorized copying, distribution, or use of the Software, via any medium,
is strictly prohibited.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF,
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+132 -135
View File
@@ -5,64 +5,99 @@
</picture> </picture>
</p> </p>
# Property Management Network <h1 align="center">🏠 Property Management Network</h1>
**Property management SaaS for independent landlords.** Track properties, tenants, rent, maintenance, leases, and expenses — all in one clean dashboard. <p align="center">
<strong>The all-in-one property-management platform for independent landlords.</strong><br>
Properties, tenants, rent, maintenance, leases, expenses, AI insights, and integrations — in one clean dashboard.
</p>
Built with Next.js 16, PostgreSQL (Drizzle ORM), Better Auth, Stripe, and OpenAI. Deploys to DigitalOcean App Platform (see [DIGITALOCEAN.md](DIGITALOCEAN.md)). <p align="center">
<img alt="Next.js" src="https://img.shields.io/badge/Next.js-16-black?logo=nextdotjs">
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white">
<img alt="PostgreSQL" src="https://img.shields.io/badge/PostgreSQL-Drizzle_ORM-4169E1?logo=postgresql&logoColor=white">
<img alt="License" src="https://img.shields.io/badge/license-Proprietary-red">
</p>
--- ---
## What it does ## ✨ Overview
Property Management Network replaces the spreadsheet + WhatsApp chaos that most small landlords live with. Key capabilities: Property Management Network replaces the spreadsheet-and-WhatsApp chaos that most small landlords live with. It gives a solo landlord or a small team a single source of truth for their whole portfolio — and the automation, AI, and integrations to run it hands-off.
- **Properties & units** — manage your entire portfolio with occupancy tracking Everything is **multi-tenant and team-aware**: each landlord operates on their own isolated portfolio, and Landlord/Lifetime accounts can invite teammates with scoped roles.
- **Tenant profiles** — contact info, lease history, payment records, and a private tenant portal
- **Rent tracking** — log payments, send Stripe payment links, auto-mark overdue balances ### 🧰 What you can do
- **Maintenance requests** — status workflow (Open → In Progress → Resolved), tenant submissions via portal
- **Lease management** — expiry countdowns, automated 60/30/7-day email alerts **Core operations**
- **Expenses** — categorized logging with recurring expense support - 🏢 **Properties & units**manage your whole portfolio with live occupancy tracking and a map view (addresses are auto-geocoded).
- **Documents** — file vault per property with drag-and-drop upload to local disk, served through an auth-gated route - 👥 **Tenants**profiles, lease history, payment records, and a private **tenant portal** (token-based, no login required).
- **AI features** — AI-powered recommendations, predictions, and impact tracking (Pro+) - 💵 **Rent tracking**log payments, send **Stripe or PayPal** payment links, and auto-mark balances overdue with automatic late fees.
- **Automated emails** — rent reminders, overdue alerts, lease expiry notifications via SMTP (SMTP2GO) - 🔧 **Maintenance**full status workflow (Open → In Progress → Resolved), with tenant-submitted requests from the portal.
- **Tenant portal** — token-based (no login), tenants can view rent history and submit maintenance - 📄 **Leases**expiry countdowns, automated 60/30/7-day email alerts, and **e-signature** (DocuSign / Dropbox Sign).
- 🧾 **Expenses** — categorized logging with recurring-expense support.
- 🗂️ **Documents** — a per-property file vault stored in object storage and served through an auth-gated route.
- 🔎 **Inspections & vendors** — move-in/out/routine inspection checklists and a vendor directory.
- 📊 **Reports & exports** — portfolio analytics with CSV export.
- 📅 **Calendar** — an in-app calendar plus a read-only **iCal (ICS) feed** you can subscribe to.
**Automation & AI**
- 🤖 **AI features** — recommendations, predictions, impact tracking, and a portfolio assistant (OpenAI). *(Pro and up.)*
- ✉️ **Automated email** — rent reminders, overdue notices, and lease-expiry alerts, plus a configurable **follow-up engine**.
- 🎨 **White-label branding** — put your own brand on the tenant portal. *(Landlord / Lifetime.)*
- 🛡️ **Admin dashboard** — superadmin tools with a full audit log.
--- ---
## Revenue model ## 🔌 Integrations & developer platform
| Plan | Price | Limits | | Capability | Details |
|------|-------|--------| |---|---|
| Starter | Free | 1 property, 3 tenants, no AI | | 🌐 **Public REST API** | Versioned `/api/v1` endpoints (properties, tenants, payments, maintenance, webhooks) authenticated with Bearer **API keys**. See `/api-docs`. |
| Pro | $29/mo | 10 properties, unlimited tenants, AI (50 calls/mo) | | 🪝 **Outbound webhooks / Zapier** | Subscribe to events (`tenant.created`, `payment.paid`, `maintenance.updated`, …). Deliveries are **HMAC-signed**, retried with backoff, and Zapier-compatible via the REST-hook subscribe/unsubscribe pattern. |
| Landlord | $59/mo | Unlimited properties, team access, white-label, AI (200/mo) | | 💳 **Payments** | Stripe (subscriptions + rent payment links) and PayPal. |
| Lifetime | $199 one-time | Everything in Landlord, forever | | 📚 **Accounting sync** | One-way push of income & expenses to **QuickBooks Online** or **Xero** (OAuth). |
| ✍️ **E-signature** | Send leases for signature via **DocuSign** or **Dropbox Sign**. |
| 🔑 **Auth** | Email/password and Google OAuth (Better Auth). |
Subscription billing via Stripe. Lifetime deal is ideal for Flippa buyers who want to offer an LTD to early customers. Every integration is env-gated: unconfigured providers show a clean “not configured” state instead of a broken button.
--- ---
## Tech stack ## 💳 Plans & pricing
| Layer | Tech | | Plan | Price | Highlights |
|-------|------| |------|-------|------------|
| Framework | Next.js 16.2 (App Router, TypeScript) | | 🆓 **Starter** | Free | 1 property, 3 tenants, no AI |
| Styling | Tailwind CSS + Geist font | | 🚀 **Pro** | $29/mo | 10 properties, unlimited tenants, AI (50 calls/mo) |
| Database | PostgreSQL (via Drizzle ORM) | | 🏆 **Landlord** | $59/mo | Unlimited properties, team access, white-label, AI (200/mo) |
| ♾️ **Lifetime** | $199 once | Everything in Landlord, forever |
Billing runs through **Stripe** or **PayPal**. Stripe products/prices are resolved by stable lookup keys and auto-created on first checkout, so going live is just an API-key swap — no price IDs to wire up.
---
## 🧱 Tech stack
| Layer | Technology |
|-------|------------|
| Framework | Next.js 16.2 (App Router, TypeScript, React 19) |
| Styling | Tailwind CSS + Geist |
| Database | PostgreSQL via **Drizzle ORM** |
| Auth | Better Auth (email/password + Google OAuth) | | Auth | Better Auth (email/password + Google OAuth) |
| Storage | DigitalOcean Spaces (S3-compatible, CDN, auth-gated) | | Object storage | DigitalOcean Spaces (S3-compatible, CDN, auth-gated) |
| Payments | Stripe (subscriptions + payment links) | | Payments | Stripe + PayPal |
| AI | OpenAI (gpt-4o-mini) | | AI | OpenAI (`gpt-4o-mini`) |
| Email | SMTP (SMTP2GO) | | Email | SMTP (SMTP2GO) |
| Maps | Leaflet + OpenStreetMap / Nominatim geocoding |
| Cron | DigitalOcean Functions (scheduled triggers) | | Cron | DigitalOcean Functions (scheduled triggers) |
| Deploy | DigitalOcean App Platform (Docker image via DOCR) | | Deploy | DigitalOcean App Platform (Docker image via DOCR) |
--- ---
## Setup ## 🚀 Getting started
### 1. Clone and install ### 1. Clone & install
```bash ```bash
git clone <your-repo> git clone <your-repo>
@@ -70,155 +105,117 @@ cd property-management-network
npm install npm install
``` ```
### 2. Configure environment variables ### 2. ⚙️ Configure environment
Copy the template and fill in your own values:
```bash ```bash
cp .env.example .env.local cp .env.example .env.local
``` ```
Fill in `.env.local`: `.env.local` holds your database URL, auth secret, and credentials for Stripe/PayPal, OpenAI, SMTP, and object storage. **Every variable is documented inline in `.env.example`**, and the full production reference lives in **[DIGITALOCEAN.md](DIGITALOCEAN.md)**. Never commit real secrets.
```env ### 3. 🗄️ Run migrations
# Database (PostgreSQL via Drizzle ORM)
DATABASE_URL=
# Auth (Better Auth) The schema is managed by Drizzle (see `drizzle.config.ts`). Point `DATABASE_URL` at your PostgreSQL instance, then:
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-random-secret-string
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# File storage (local disk)
STORAGE_DIR=./storage
# Stripe (no price IDs needed — resolved by lookup key, auto-created on first checkout)
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
# OpenAI
OPENAI_API_KEY=
# Email (SMTP — e.g. SMTP2GO)
SMTP_HOST=mail.smtp2go.com
SMTP_PORT=2525
SMTP_USER=
SMTP_PASS=
EMAIL_FROM=postmaster@yourdomain.com
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000
CRON_SECRET=your-random-secret-string
```
### 3. Run database migrations
The schema is managed with Drizzle ORM (see `drizzle.config.ts`). Point `DATABASE_URL` at your PostgreSQL instance in `.env.local`, then apply the migrations from `lib/db/migrations`:
```bash ```bash
npm run db:migrate npm run db:migrate # apply migrations
npm run db:generate # regenerate after schema changes
npm run db:push # push schema directly (quick local prototyping)
``` ```
To regenerate migrations after changing the schema, use `npm run db:generate`. For quick local prototyping you can push the schema directly with `npm run db:push`. ### 4. 🔌 Wire up services (as needed)
### 4. Configure Stripe - **Stripe** — set the API keys, then add a webhook at `https://yourdomain.com/api/stripe/webhook` for `checkout.session.completed`, the `customer.subscription.*` events, `invoice.payment_failed`, and `payment_intent.succeeded`.
- **Email** — verify a sending domain with your SMTP provider (e.g. SMTP2GO) and set the `SMTP_*` + `EMAIL_FROM` vars.
- **Google / PayPal / OpenAI / accounting / e-sign** — each is optional and activates once its env vars are present.
Add your API keys (`STRIPE_SECRET_KEY`, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`) — that's it. Products and prices are resolved by stable **lookup keys** and auto-created on first checkout (Pro $29/mo, Landlord $59/mo, Lifetime $199, plus annual), so there are **no price IDs to configure** and going live is just an API-key swap. To pre-create the catalog, optionally run `node scripts/stripe-setup.mjs`. ### 5. ▶️ Run locally
Set up a webhook at `https://yourdomain.com/api/stripe/webhook` listening to:
- `checkout.session.completed`
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `invoice.payment_failed`
- `payment_intent.succeeded`
### 5. Configure email (SMTP)
Use any SMTP provider (e.g. SMTP2GO). Verify your sending domain with the provider, then set `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, and `EMAIL_FROM`.
### 6. (Optional) Google OAuth
Create OAuth credentials in the Google Cloud Console and set `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` to enable Google sign-in via Better Auth.
### 7. Run locally
```bash ```bash
npm run dev npm run dev
``` ```
Open [http://localhost:3000](http://localhost:3000). Open **[http://localhost:3000](http://localhost:3000)**.
### 8. Deploy (DigitalOcean App Platform) ### 6. 🚢 Deploy
The repo ships a production `Dockerfile` (Next.js standalone output), an App Platform spec at [`.do/app.yaml`](.do/app.yaml), DO Functions cron under [`functions/`](functions/), and a `/api/health` liveness probe. See **[DIGITALOCEAN.md](DIGITALOCEAN.md)** for the full walkthrough: build/push the image to DOCR, create the app, wire up Managed Postgres + Spaces, and deploy the scheduled cron functions. The repo ships a production `Dockerfile` (Next.js standalone), an App Platform spec at [`.do/app.yaml`](.do/app.yaml), DO Functions cron under [`functions/`](functions/), and a `/api/health` probe. Follow **[DIGITALOCEAN.md](DIGITALOCEAN.md)** for the full walkthrough.
--- ---
## Project structure ## 🗂️ Project structure
``` ```
app/ app/
├── (marketing)/ # Landing page, pricing, legal ├── (marketing)/ # Landing page, pricing, legal, API docs
├── (auth)/ # Login, signup, password reset ├── (auth)/ # Login, signup, password reset
├── (dashboard)/ # All dashboard pages (auth-gated) ├── (dashboard)/ # Auth-gated app (properties, tenants, rent, maintenance,
├── dashboard/ # Overview + stats # leases, expenses, inspections, vendors, reports,
├── properties/ # Property + unit management # calendar, AI, onboarding, settings)
│ ├── tenants/ # Tenant profiles ├── (admin)/ # Superadmin dashboard
│ ├── rent/ # Payment tracking
│ ├── maintenance/ # Maintenance requests
│ ├── leases/ # Lease tracking
│ ├── expenses/ # Expense logging
│ └── settings/ # Billing + profile
├── api/ ├── api/
│ ├── properties/ # CRUD │ ├── v1/ # 🌐 Public REST API (Bearer API keys)
│ ├── tenants/ # CRUD + auto unit assignment │ ├── webhooks + cron/ # 🪝 Outbound webhook delivery + scheduled jobs
│ ├── rent/ # CRUD + Stripe payment links │ ├── stripe/ paypal/ # 💳 Billing + payment links + provider webhooks
│ ├── maintenance/ # CRUD + status workflow │ ├── integrations/ # 📚 QuickBooks / Xero OAuth
│ ├── leases/ # CRUD │ ├── esign/ # ✍️ DocuSign / Dropbox Sign
── expenses/ # CRUD ── # Properties, tenants, rent, maintenance, documents, AI
│ ├── documents/ # Document metadata (files on local disk)
│ ├── ai/ # Rent receipts + maintenance summaries
│ ├── notifications/ # Send emails via SMTP (SMTP2GO)
│ ├── stripe/ # Checkout, portal, webhook
│ └── cron/ # Rent reminders + lease expiry alerts
└── tenant-portal/[token]/ # Public tenant portal (no login) └── tenant-portal/[token]/ # Public tenant portal (no login)
lib/ lib/
├── db/ # Drizzle schema, queries, migrations ├── db/ # Drizzle schema, queries, migrations
├── auth.ts # Better Auth config ├── auth.ts account.ts # Better Auth + team/account scoping
├── storage.ts # Local-disk file storage helpers ├── storage.ts # Object storage (Spaces) with local-disk dev fallback
├── stripe/ # Client, plans, payment links ├── webhooks/ # Event catalog, HMAC signing, SSRF guard, delivery
├── stripe/ paypal/ # Billing clients & plans
├── accounting/ esign/ # QuickBooks/Xero & DocuSign/Dropbox Sign
├── ai/ # OpenAI client + prompts ├── ai/ # OpenAI client + prompts
├── email/ # SMTP (SMTP2GO) client + HTML templates ├── email/ # SMTP (SMTP2GO) client + HTML templates
└── validations/ # Zod schemas for all entities └── validations/ # Zod schemas for all entities
drizzle.config.ts # Drizzle ORM config (DATABASE_URL, migrations dir)
``` ```
--- ---
## Database schema ## 🗄️ Data model & isolation
11 tables, managed via Drizzle ORM: The schema spans **~30 tables** managed via Drizzle ORM, grouped roughly as:
`profiles` · `properties` · `units` · `tenants` · `rent_payments` · `maintenance_requests` · `leases` · `expenses` · `documents` · `notifications` · `usage_events` - **Core** — `profiles`, `properties`, `units`, `tenants`, `rent_payments`, `maintenance_requests`, `leases`, `expenses`, `documents`, `inspections`, `vendors`
- **Automation & AI** — `notifications`, `follow_up_rules`, `follow_up_log`, `ai_recommendations`, `ai_predictions`, `activity_log`, `usage_events`
- **Accounts & platform** — `account_members`, `api_keys`, `app_settings`, `admin_audit_log`, `accounting_connections`, `signature_requests`, `webhook_endpoints`, `webhook_deliveries`
- **Auth (Better Auth)** — `user`, `session`, `account`, `verification`
Data isolation is enforced in the application layer: every API route authenticates via `getSessionUser()` and scopes its queries by `user_id`. There is no database-level RLS, so this query scoping must be maintained carefully on every new route and query. > 🔐 **Tenancy is enforced in the application layer.** Every query scopes by the resolved **account owner id** (team-aware), never the raw session user. There is no database RLS, so this scoping must be preserved on every new route — see `lib/account.ts` (`getEffectiveOwnerId`).
--- ---
## Cron jobs ## ⏰ Scheduled jobs
| Job | Schedule | What it does | Cron is driven by DigitalOcean Functions hitting `CRON_SECRET`-protected endpoints (`functions/project.yml`):
|-----|----------|--------------|
| Rent reminders | Daily 9am UTC | Marks overdue payments, sends 3-day reminder emails |
| Lease expiry | Daily 10am UTC | Sends 60/30/7-day expiry alerts to landlord |
Cron routes are protected with `CRON_SECRET` (Bearer token in `Authorization` header). | Job | Schedule (UTC) | What it does |
|-----|----------------|--------------|
| `daily` | 09:00 | Rent reminders, overdue marking, 60/30/7-day lease-expiry alerts |
| `late-fees` | 08:00 | Applies late fees past the grace period |
| `follow-ups` | 10:00 | Runs each account's active follow-up rules |
| `webhooks` | every 5 min | Retries pending outbound webhook deliveries |
--- ---
## License ## 🔒 Security highlights
MIT - 🔑 API keys are stored as SHA-256 hashes; the plaintext is shown once.
- 🪝 Webhook payloads are **HMAC-SHA256 signed** (`X-PMN-Signature`); endpoint URLs are **SSRF-guarded** (private/loopback/metadata ranges blocked).
- 📁 Uploaded files are served only through an auth-gated route; object storage is required in production (uploads **fail loud** rather than silently hit ephemeral disk).
- 🛢️ Verified TLS to Postgres in production (`DATABASE_SSL=require` + CA).
- ⏱️ Cron endpoints use a constant-time bearer check and fail closed.
---
## 📜 License
**Proprietary — © 2026 Property Management Network. All rights reserved.**
This source code is proprietary and confidential. No license or permission is granted to use, copy, modify, merge, publish, distribute, sublicense, or sell any part of it without the prior written consent of the copyright holder. See [LICENSE](LICENSE).
+1
View File
@@ -2,6 +2,7 @@
"name": "property-management-network", "name": "property-management-network",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"license": "UNLICENSED",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",