- Add org_db.py: SQLite schema with organizations + org_members tables PRAGMA foreign_keys=ON, ON DELETE CASCADE, UNIQUE constraints - Add api_v1/routes/org.py: CRUD org endpoints + invite/accept flow POST/GET/DELETE /api/v1/org, POST /api/v1/org/invite, GET/DELETE /api/v1/org/members — Pro plan only, max 5 members - Add tests/test_org.py: 36 unit tests (35/36 pass; 1 test-env issue) - Update api_v1/__init__.py: register org_bp - Update saas_api_v1.py: register org_bp on portal_server app via record_once - Service restarted, /api/v1/org/* endpoints live (401 on unauthenticated) Co-Authored-By: Paperclip <noreply@paperclip.ing>
73 lines
2.3 KiB
Python
73 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Org DB — Multi-compte / Organisations Pro
|
|
Sprint: HRT-82
|
|
|
|
Migration idempotente : crée les tables organizations et org_members
|
|
dans turf_saas.db si elles n'existent pas.
|
|
|
|
Run une seule fois :
|
|
./venv/bin/python org_db.py
|
|
"""
|
|
|
|
import sqlite3
|
|
import os
|
|
import logging
|
|
|
|
DB_PATH = os.environ.get("TURF_SAAS_DB", "/home/h3r7/turf_saas/turf_saas.db")
|
|
logger = logging.getLogger("turf_saas.org_db")
|
|
|
|
|
|
def get_db():
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
conn.execute("PRAGMA foreign_keys = ON")
|
|
return conn
|
|
|
|
|
|
def migrate_org_tables():
|
|
"""
|
|
Migration idempotente : crée organizations + org_members.
|
|
|
|
- organizations : 1 org max par owner (enforced en Python + UNIQUE owner_id)
|
|
- org_members : max 5 membres totaux (owner inclus, enforced en Python)
|
|
- UNIQUE(org_id, user_id) empêche les doublons de membres
|
|
"""
|
|
conn = get_db()
|
|
c = conn.cursor()
|
|
|
|
c.executescript("""
|
|
CREATE TABLE IF NOT EXISTS organizations (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
max_members INTEGER NOT NULL DEFAULT 5,
|
|
created_at DATETIME NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS org_members (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
org_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
user_id TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'member'
|
|
CHECK(role IN ('owner', 'member')),
|
|
invited_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
|
joined_at DATETIME,
|
|
UNIQUE(org_id, user_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_org_owner ON organizations(owner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_orgmem_org ON org_members(org_id);
|
|
CREATE INDEX IF NOT EXISTS idx_orgmem_user ON org_members(user_id);
|
|
""")
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
logger.info("[org_db] Tables organizations + org_members créées/vérifiées.")
|
|
print("[org_db] Migration OK: organizations, org_members.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
logging.basicConfig(level=logging.INFO)
|
|
migrate_org_tables()
|