- docs/PLAN.md - plan arhitectural complet (creat anterior) - docs/api-contract.json - contract API intre backend/frontend agenti - docs/superpowers/plans/2026-03-13-roaauto-implementation.md - plan implementare cu Agent Teams - HANDOFF.md - context pentru sesiuni viitoare Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
26 KiB
Plan: ROA AUTO SaaS - Proiect Nou Separat
Context
Marius (ROMFAST SRL, Constanta) construieste un SaaS pentru service-uri auto mici si vulcanizari din Romania. Serverul initial e pe Proxmox la birou (nu VPS cloud), deci aplicatia trebuie sa functioneze si cand serverul e offline. Cand proiectul se autosustine financiar, se muta pe VPS cloud.
Exista un prototip in roa2web/roa-auto-mobile/ (Ionic Vue + SQLite server, single-tenant, fara auth) - folosit doar ca referinta conceptuala. Proiect nou de la zero, repository git separat.
Decizii Confirmate
| Decizie | Valoare |
|---|---|
| Nume proiect | roaauto |
| Director | E:\proiecte\roaauto\ |
| Git remote | gitea.romfast.ro:marius/roaauto |
| Domeniu | roaauto.romfast.ro |
| CSS framework | Tailwind CSS |
| Cod existent | De la zero (prototip doar referinta) |
| DB server | libSQL (fisier, in-process cu FastAPI) |
| DB browser | wa-sqlite (SQLite WASM) |
| Sync | Custom simplu (timestamp-based) |
| Offline | Da - reads + writes locale, sync cand e net |
Arhitectura
[Browser] [Server Proxmox / VPS]
┌─────────────────────┐ ┌──────────────────────┐
│ Vue 3 PWA │ │ FastAPI │
│ wa-sqlite (WASM) │ ◄──── sync ────► │ libSQL (fisier) │
│ └── OPFS storage │ │ └── roaauto.db │
│ └── _sync_queue │ │ │
│ └── ALL reads local│ │ WeasyPrint (PDF) │
│ └── writes instant │ │ SMSAPI.ro (SMS) │
│ │ │ JWT auth │
│ Service Worker │ │ │
│ └── app shell cache│ │ Cloudflare Tunnel │
└─────────────────────┘ └──────────────────────┘
Cum functioneaza sync-ul
Initial sync (la login):
GET /api/sync/full?tables=orders,order_lines,vehicles,catalog_*
→ Server returneaza toate datele tenant-ului
→ Client INSERT-eaza in wa-sqlite local
Write (offline OK):
1. User creeaza comanda
2. INSERT in wa-sqlite local (instant, UUID generat client-side)
3. INSERT in _sync_queue (table, id, operation, data, timestamp)
4. Daca online: POST /api/sync/push → server valideaza → INSERT in libSQL
5. Daca offline: queue asteapta → sync la reconectare
Incremental sync (periodic, cand e online):
GET /api/sync/changes?since=2026-03-13T10:00:00
→ Server returneaza doar randurile modificate dupa timestamp
→ Client UPSERT in wa-sqlite local
Conflict resolution: Server wins (last-write-wins). Simplu, predictibil.
De ce libSQL pe server (nu PostgreSQL)
- Un fisier (
roaauto.db) - nu un server/container separat - In-process - FastAPI acceseaza direct, zero overhead retea
- Replication built-in - backup = copie fisier sau libSQL replication
- Schema identica cu wa-sqlite din browser (ambele SQLite)
- Resurse minime - perfect pentru Proxmox la birou
- Migrare la PostgreSQL cand cresc cerintele (change connection string + fix SQL specifics)
Limitari acceptate:
- Write concurrency: un writer la un moment dat (WAL mode). OK pentru <50 useri.
- ALTER TABLE limitat: Alembic batch operations rezolva asta.
- No RLS: izolare multi-tenant application-level (WHERE tenant_id = ?).
Tech Stack
| Layer | Tehnologie | De ce |
|---|---|---|
| Backend | FastAPI + SQLAlchemy 2.0 + Alembic | Known territory |
| DB server | libSQL (via aiosqlite dialect) |
Fisier, in-process, replication |
| DB browser | wa-sqlite (@journeyapps/wa-sqlite) |
SQLite WASM, OPFS persistence |
| Sync | Custom (timestamp-based push/pull) | Simplu, fara dependente extra |
| Auth | python-jose + passlib (bcrypt) | JWT 30 zile, offline valid |
| WeasyPrint | Server-side, diacritice ROM OK | |
| SMS | SMSAPI.ro | Provider romanesc, REST API |
| Frontend | Vue 3 + Tailwind CSS 4 | Responsive, utility-first |
| State | Pinia + reactive SQL queries | Local-first |
| PWA | vite-plugin-pwa | App shell cache, install prompt |
| Build | Vite 6 | Fast |
| Deploy | Docker (single container) + Dokploy + Cloudflare Tunnel | Self-hosted Proxmox |
| Git | Gitea (gitea.romfast.ro) | Self-hosted |
Structura Proiect
E:\proiecte\roaauto\
├── docker-compose.yml # Backend (FastAPI + libSQL) + frontend build
├── docker-compose.dev.yml
├── Makefile # make dev, make migrate, make seed, make backup
├── .env.example
│
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── alembic.ini
│ ├── alembic/versions/
│ ├── app/
│ │ ├── main.py # FastAPI + lifespan
│ │ ├── config.py # Settings din env
│ │ ├── deps.py # get_db, get_current_user, get_tenant_id
│ │ │
│ │ ├── auth/
│ │ │ ├── router.py # register, login, refresh
│ │ │ ├── service.py # JWT 30 zile, bcrypt, trial logic
│ │ │ └── schemas.py
│ │ │
│ │ ├── sync/
│ │ │ ├── router.py # GET /sync/full, GET /sync/changes, POST /sync/push
│ │ │ └── service.py # Sync logic, conflict resolution
│ │ │
│ │ ├── tenants/ # settings, subscription
│ │ ├── users/ # invite, list, deactivate
│ │ ├── vehicles/ # CRUD
│ │ ├── orders/ # CRUD + workflow
│ │ │ ├── router.py
│ │ │ ├── service.py # DRAFT->VALIDAT->FACTURAT, recalc totals
│ │ │ └── schemas.py
│ │ ├── invoices/ # create from order
│ │ ├── catalog/ # nomenclatures
│ │ ├── appointments/ # CRUD
│ │ ├── client_portal/ # GET /p/{token} (public, no auth)
│ │ ├── sms/ # SMSAPI.ro
│ │ ├── pdf/
│ │ │ ├── service.py # WeasyPrint
│ │ │ └── templates/ # deviz.html, factura.html
│ │ │
│ │ └── db/
│ │ ├── base.py # SQLAlchemy Base, UUID mixin, TenantMixin
│ │ ├── session.py # libSQL/aiosqlite engine + session
│ │ └── models/ # tenant, user, vehicle, order, etc.
│ │
│ ├── data/ # libSQL database file(s)
│ │ └── .gitkeep
│ └── tests/
│
├── frontend/
│ ├── Dockerfile + nginx.conf
│ ├── package.json
│ ├── vite.config.js
│ ├── tailwind.config.js
│ ├── index.html
│ │
│ └── src/
│ ├── main.js # Vue + Pinia + wa-sqlite init
│ ├── App.vue
│ │
│ ├── db/
│ │ ├── schema.js # Tabele SQLite locale (mirror server)
│ │ ├── database.js # wa-sqlite init + OPFS
│ │ ├── sync.js # Sync engine (full sync, incremental, push queue)
│ │ └── queries.js # Reactive query helper
│ │
│ ├── router/index.js
│ │
│ ├── stores/
│ │ ├── auth.js # JWT, login/register, offline token validation
│ │ ├── orders.js # SQL queries pe wa-sqlite local
│ │ ├── vehicles.js
│ │ ├── catalog.js
│ │ └── sync.js # Sync status, last sync time
│ │
│ ├── composables/
│ │ ├── useAuth.js
│ │ ├── useBreakpoint.js
│ │ ├── useSqlQuery.js # Reactive SQL query (re-run on table change)
│ │ └── useSync.js # Online/offline status, trigger sync
│ │
│ ├── layouts/
│ │ ├── AppLayout.vue # Sidebar desktop / bottom nav mobile
│ │ ├── AuthLayout.vue
│ │ └── ClientLayout.vue # Portal client (minimal)
│ │
│ ├── views/
│ │ ├── auth/ # Login, Register, InviteAccept
│ │ ├── dashboard/ # Today's orders, quick stats
│ │ ├── orders/ # List, Create, Detail, Invoice
│ │ ├── vehicles/ # List, Detail
│ │ ├── appointments/ # Calendar/list
│ │ ├── catalog/ # Tabbed: marci, norme, preturi
│ │ ├── settings/ # Profile, Users, Subscription
│ │ └── client/ # DevizView (public), AppointmentBook
│ │
│ ├── components/
│ │ ├── common/ # Button, Input, Modal, Table, StatusBadge, SyncIndicator
│ │ ├── orders/ # OrderCard, OperationForm, MaterialForm, OrderTotals
│ │ └── vehicles/ # VehiclePicker
│ │
│ └── assets/css/
│ └── main.css # Tailwind imports
│
└── docs/
├── ARCHITECTURE.md
└── CLAUDE.md # Reguli pentru Claude Code in proiectul nou
Schema Database (identica server + browser)
Toate tabelele: id TEXT PRIMARY KEY (UUID v7 generat client-side), tenant_id TEXT NOT NULL, oracle_id INTEGER (NULL, mapare la Oracle ROAAUTO), updated_at TEXT (ISO timestamp pentru sync).
-- Tenants & Users
tenants (id, nume, cui, reg_com, adresa, telefon, email, iban, banca,
plan, trial_expires_at, created_at, updated_at)
users (id, tenant_id, email, password_hash, nume, rol, activ,
created_at, updated_at)
invites (id, tenant_id, email, rol, token, expires_at, accepted_at,
created_by, created_at)
-- Nomenclatures
catalog_marci (id, tenant_id, denumire, activ)
catalog_modele (id, marca_id, denumire)
catalog_ansamble (id, tenant_id, denumire)
catalog_norme (id, tenant_id, cod, denumire, ore_normate, ansamblu_id)
catalog_preturi (id, tenant_id, denumire, pret, um)
catalog_tipuri_deviz (id, tenant_id, denumire)
catalog_tipuri_motoare (id, tenant_id, denumire)
mecanici (id, tenant_id, user_id, nume, prenume, activ)
-- Core Business
vehicles (id, tenant_id, client_nume, client_telefon, client_email,
client_cod_fiscal, client_adresa, nr_inmatriculare,
marca_id, model_id, an_fabricatie, serie_sasiu,
tip_motor_id, created_at, updated_at)
orders (id, tenant_id, nr_comanda, data_comanda, vehicle_id,
tip_deviz_id, status, km_intrare, observatii,
-- client snapshot (denormalized)
client_nume, client_telefon, nr_auto, marca_denumire, model_denumire,
-- totals
total_manopera, total_materiale, total_general,
-- client portal
token_client,
created_by, created_at, updated_at)
order_lines (id, order_id, tenant_id, tip, descriere,
norma_id, ore, pret_ora, -- manopera
um, cantitate, pret_unitar, -- material
total, mecanic_id, ordine, created_at, updated_at)
invoices (id, tenant_id, order_id, nr_factura, serie_factura,
data_factura, modalitate_plata,
client_nume, client_cod_fiscal, nr_auto,
total_fara_tva, tva, total_general, created_at, updated_at)
appointments (id, tenant_id, vehicle_id, client_nume, client_telefon,
data_ora, durata_minute, observatii, status, order_id,
created_at, updated_at)
-- Sync (doar in browser, nu pe server)
_sync_queue (id, table_name, row_id, operation, data_json, created_at, synced_at)
_sync_state (table_name, last_sync_at)
Faze de Implementare
Faza 0: Setup Proiect (aceasta sesiune)
Livrabil: Director creat, plan salvat in proiect
- Creeaza directorul
E:\proiecte\roaauto\ - Salveaza acest plan ca
E:\proiecte\roaauto\docs\PLAN.mdpentru referinte viitoare - STOP - nu continua cu implementarea
Faza 1: Fundatie + Auth + Sync (Saptamana 1-2)
Livrabil: Register/Login, wa-sqlite local, sync functional
Backend:
git initlaE:\proiecte\roaauto\, remote pe Gitea- FastAPI skeleton:
main.py,config.py,deps.py - libSQL setup:
session.pycuaiosqlite - Alembic init + prima migrare:
tenants,users,invites - Auth:
POST /auth/register(creeaza tenant + owner),POST /auth/login(JWT 30 zile) - Sync endpoints:
GET /sync/full,GET /sync/changes?since=,POST /sync/push - JWT middleware:
get_current_user,get_tenant_id
Frontend:
8. Vue 3 + Vite + Tailwind + Pinia scaffold
9. wa-sqlite setup: database.js (init WASM + OPFS), schema.js (create tables)
10. Sync engine: sync.js (full sync, incremental sync, push queue)
11. Router cu auth guards
12. Pages: Login, Register
13. App layout responsive (sidebar desktop, bottom nav mobile)
14. Sync status indicator (online/offline/syncing)
Verificare: Register → login → full sync → datele apar in wa-sqlite local → offline: app functioneaza → reconectare: sync push
Faza 2: Business Logic Core (Saptamana 3-4)
Livrabil: Creare comanda cu operatii/materiale, workflow complet
- Models + migrations:
vehicles,orders,order_lines,catalog_*,mecanici - Seed data (Alembic): 24 marci, 11 ansamble, 6 tipuri deviz, 5 tipuri motoare, 3 preturi
- OrderService: CRUD + workflow DRAFT→VALIDAT→FACTURAT + recalc totals
- wa-sqlite schema update (mirror noile tabele)
- Frontend: Dashboard (comenzi azi, stats)
- Frontend: Orders list (query local SQLite, filtrare, search)
- Frontend: Order create (vehicle picker, operatii, materiale, totals live)
- Frontend: Order detail (view/edit, status actions)
- Frontend: Catalog management (tabbed)
- Frontend: Vehicle picker (search-as-you-type pe SQLite local)
Verificare: Creare comanda offline → operatii + materiale → totals corecte → reconectare → sync → comanda pe server
Faza 3: Facturare + PDF (Saptamana 5)
Livrabil: Deviz/factura PDF
- Model
invoices, InvoiceService (VALIDAT→FACTURAT, TVA 19%) - WeasyPrint templates HTML/CSS (deviz.html, factura.html cu diacritice)
- Endpoint
GET /api/orders/{id}/pdf/deviz - Frontend: Invoice page, PDF download/share
Verificare: DRAFT → operatii → VALIDAT → factura → PDF cu diacritice corecte
Faza 4: Portal Client + SMS (Saptamana 6)
Livrabil: Client primeste SMS, vede deviz, accepta/refuza
GET /api/p/{token}(public, no auth) → deviz dataPOST /api/p/{token}/accept,/reject- SMSAPI.ro integration
- Frontend: DevizViewPage (public, mobile-first)
- Frontend: "Trimite deviz" button (SMS + WhatsApp link)
- Model
appointments+ CRUD + client booking
Verificare: Comanda → trimite SMS → client link → accepta → status updated
Faza 5: Management Angajati + Settings (Saptamana 7)
Livrabil: Owner invita angajati, role-based access
- Invite system (email link 48h)
- User management (list, deactivate, roles)
- Settings: profil service
- Role-based UI (mecanic vede doar comenzile lui)
Faza 6: Deployment (Saptamana 8)
Livrabil: roaauto.romfast.ro live
- Dockerfile (FastAPI + libSQL, single container)
- Frontend Dockerfile (nginx)
- docker-compose.yml
- Dokploy pe Proxmox + Cloudflare Tunnel
- Trial expiry middleware
- Backup strategy (libSQL replication / cron cp)
Faza 7: Polish + PWA (Saptamana 9-10)
Livrabil: Production-ready, instalabil
- PWA: service worker, install prompt, icons
- Sync indicator UI (online/offline/syncing/error)
- Error handling, toasts, loading states
- Responsive testing (phone, tablet, desktop)
- Reports: sumar lunar, export CSV
Referinta din Prototip (doar consultare)
| Ce | Fisier | Ce portam conceptual |
|---|---|---|
| Workflow | roa-auto-mobile/backend/services/order_service.py |
State machine, _recalc_totals |
| Seed data | roa-auto-mobile/backend/seed.py |
24 marci, ansamble, tipuri |
| PDF layout | roa-auto-mobile/src/services/pdf.js |
Format coloane, header, totals |
| UI flow | roa-auto-mobile/src/views/OrderCreatePage.vue |
Flow creare comanda |
| Vehicle search | roa-auto-mobile/src/components/VehiclePicker.vue |
Search pattern |
Compatibilitate ROAAUTO Oracle
SaaS-ul trebuie sa fie compatibil cu ROAAUTO VFP9+Oracle. Clientii care cresc pot migra la sistemul complet ROAAUTO cu Oracle. Coloanele SaaS mapeaza 1:1 la tabelele Oracle MARIUSM_AUTO.dev_*.
Mapare tabele SaaS → Oracle ROAAUTO
| SaaS (libSQL) | Oracle (MARIUSM_AUTO) | Note |
|---|---|---|
orders |
dev_ordl |
+tenant_id, +token_client, UUID vs Integer PK |
order_lines (tip=manopera) |
dev_oper |
SaaS unifica oper+materiale in order_lines |
order_lines (tip=material) |
dev_estimari_produse |
Acelasi tabel, filtrat pe tip |
vehicles |
dev_masiniclienti |
Renamed, aceleasi coloane client+vehicul |
catalog_marci |
dev_nom_marci |
+tenant_id |
catalog_modele |
dev_nom_masini |
Identic |
catalog_ansamble |
dev_nom_ansamble |
+tenant_id |
catalog_norme |
dev_nom_norme |
+tenant_id |
catalog_preturi |
dev_nom_preturi |
+tenant_id |
catalog_tipuri_deviz |
dev_tip_deviz |
+tenant_id |
catalog_tipuri_motoare |
dev_tipuri_motoare |
+tenant_id |
mecanici |
dev_mecanici |
+tenant_id, +user_id |
invoices |
facturi (local) |
Identic structural |
tenants |
- | Doar SaaS (nu exista in Oracle) |
users |
- | Doar SaaS |
appointments |
- | Doar SaaS (feature nou) |
Coloane compatibile (pastreaza aceleasi nume)
Coloanele business raman identice cu Oracle pentru migrare usoara:
nr_comanda,data_comanda,status,km_intrare,observatiiclient_nume,client_telefon,client_cod_fiscal,nr_automarca_denumire,model_denumiretotal_manopera,total_materiale,total_generaldenumire,ore,pret_ora,cantitate,pret_unitar,totalnr_inmatriculare,serie_sasiu,an_fabricatiecod,ore_normate,pret,umnr_factura,serie_factura,data_factura,modalitate_platatva,total_fara_tva
Coloane adaugate fata de Oracle (doar in SaaS)
id TEXT(UUID v7) - in loc deid INTEGER(Oracle sequence)tenant_id TEXT- izolare multi-tenantoracle_id INTEGER- mapare la ID-ul Oracle, NULL pana la migraretoken_client TEXT- portal client (feature SaaS)updated_at TEXT- timestamp synccreated_at TEXT- audit
Script migrare SaaS → Oracle (Phase viitoare)
# Migrare: export din libSQL SaaS → import in Oracle ROAAUTO
# 1. Map UUID → Integer (Oracle sequences)
# 2. Split order_lines → dev_oper (manopera) + dev_estimari_produse (material)
# 3. Rename tables: vehicles → dev_masiniclienti, orders → dev_ordl
# 4. Drop columns: tenant_id, token_client, oracle_id
# 5. Import nomenclatures shared (global marci etc)
Sistem Tier-uri si Business Model
Free Forever (0 RON/luna)
- 100% local - wa-sqlite in browser, ZERO cost server per user
- Toate features-urile de baza: comenzi, operatii, materiale, vehicule, nomenclator
- PDF generation client-side (jsPDF) - deviz + factura
- Web Share API - share PDF via WhatsApp/email
- Backup/restore manual - export DB ca fisier JSON/SQLite pe telefon, restore din fisier
- Un singur device (datele in browser-ul respectiv)
- Nu necesita cont pe server - app-ul se incarca ca PWA, functioneaza standalone
- Limite soft: ~50 comenzi/luna (nu blocheaza, doar sugereaza upgrade gratios)
- NU: sync cloud, client portal, SMS, multi-device, backup automat
Basic (49 RON/luna - de stabilit)
- Tot ce e in Free +
- Cloud sync (libSQL server) - datele salvate in cloud, backup automat
- Multi-device - lucreaza de pe telefon SI calculator
- Client portal - link unic pentru client sa vada devizul
- WhatsApp link - generare link deviz pentru WhatsApp (gratuit)
- 1 user
Pro (99 RON/luna - de stabilit)
- Tot ce e in Basic +
- SMS deviz - trimite SMS cu link via SMSAPI.ro
- Multi-user - pana la 3 useri (owner + 2 angajati)
- Programari - modul appointments
- Rapoarte - sumar lunar, export CSV
- Priority support
Cum functioneaza tehnic
Free tier (zero server cost):
1. User acceseaza roaauto.romfast.ro
2. PWA se incarca si se instaleaza (service worker cache)
3. wa-sqlite se initializeaza cu schema goala
4. User lucreaza 100% local - fara cont, fara server
5. Datele raman DOAR in browser (OPFS)
6. Daca sterge browser data → pierde tot (avertizare clara)
Upgrade Free → Basic:
1. User da click "Creeaza cont" (POST /auth/register)
2. Se creeaza tenant + user pe server
3. Full sync: datele locale se uploadeaza pe server (POST /sync/push)
4. De acum: sync bidirectional activ
5. Datele safe pe server + backup
Downgrade Basic → Free:
1. Subscription expirat sau anulat
2. Sync se opreste (server refuza sync requests)
3. Datele locale raman in browser (functioneaza ca inainte)
4. Datele pe server se pastreaza 90 zile (re-upgrade posibil)
5. Dupa 90 zile: datele server se sterg
Trial flow:
1. Register → 30 zile trial Basic gratuit
2. Sync activ, toate features-urile Basic
3. La expirare: downgrade automat la Free
4. Datele locale raman, sync se opreste
5. "Upgrade" button prominent
Backup manual (Free tier)
Export: wa-sqlite → JSON blob → download ca fisier "roaauto-backup-2026-03-13.json"
- Buton "Salveaza backup" in Settings
- Foloseste File System Access API sau download fallback
- Include: toate tabelele, settings, metadata
Restore: fisier JSON → parse → INSERT in wa-sqlite
- Buton "Restaureaza din backup" in Settings
- Confirmare: "Asta va inlocui datele curente. Continui?"
- Validare: verifica schema version, integritate date
UX important: La fiecare 7 zile fara backup, reminder subtil (banner, nu modal): "Ultima salvare: acum 12 zile. Salveaza un backup pe telefon."
Limite soft + sugestii gratioase (stil Revolut)
Filosofie: Nu blocam NICIODATA. Doar sugeram, pozitiv, la momentul potrivit.
Limite soft Free:
- ~50 comenzi/luna (numar orientativ, nu hard limit)
- Fara limita pe vehicule/nomenclator
Cum arata sugestiile (non-intrusive, pozitive):
| Moment | Tip | Mesaj (exemplu) |
|---|---|---|
| Comanda #20 | Banner mic, dismissable | "Ai creat 20 de comenzi luna asta. Stiai ca poti sincroniza datele pe mai multe dispozitive?" |
| Comanda #40 | Card in dashboard | "Afacerea ta creste! Cu planul Basic ai backup automat si poti trimite devize direct clientilor." |
| Comanda #50 | Bottom sheet (o singura data) | "Ai atins 50 de comenzi. Felicitari! Activeaza planul Basic pentru sync, backup automat si portal client. Continui gratuit, fara restrictii." |
| Share PDF manual | Tooltip subtil | "Poti trimite devizul direct pe WhatsApp clientului cu un click. Vezi planul Basic." |
| Dupa 3 luni activ | Card in dashboard | "Folosesti ROA AUTO de 3 luni. Multumim! Protejeaza-ti datele cu backup automat in cloud." |
Reguli:
- Fiecare sugestie apare MAX o data (dismiss = nu mai apare niciodata)
- Niciodata popup/modal blocant
- Ton pozitiv: "Felicitari", "Afacerea ta creste", "Stiai ca..."
- Buton "Nu, multumim" vizibil si respectat
- Nu afiseaza mai mult de 1 sugestie pe zi
- Dupa dismiss: minimum 7 zile pana la urmatoarea sugestie
Logica in cod
// Frontend: check tier before feature
const tier = authStore.tier // 'free', 'basic', 'pro' (din JWT sau local)
// Sync: doar paid
if (tier !== 'free') {
await syncEngine.start() // start bidirectional sync
}
// Client portal: doar basic+
if (tier === 'free') {
showUpgradeModal('client_portal')
} else {
generateClientLink(order.token_client)
}
// SMS: doar pro
if (tier !== 'pro') {
showUpgradeModal('sms')
}
# Backend: middleware tier check
@app.middleware("http")
async def check_subscription(request, call_next):
if request.url.path.startswith("/api/sync"):
tenant = await get_tenant(request)
if tenant.plan == "free" or tenant.is_expired():
return JSONResponse(status_code=402, content={"detail": "Upgrade required"})
return await call_next(request)
Migrare viitoare la PostgreSQL
Cand proiectul creste si se muta pe VPS cloud:
- Schimba
aiosqlite→asyncpginsession.py - Adapteaza Alembic migrations (remove batch operations)
- Adauga PostgreSQL RLS pentru izolare tenant
- Optional: adauga PowerSync pentru sync automat (inlocuieste custom sync)
- Schema e compatibila - aceleasi tabele, aceleasi coloane