feat: add per-project portal gate columns + migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Database migration: Project portal auth (Phase 1).
|
||||
|
||||
Adds the per-project portal gate columns to `projects`:
|
||||
- portal_enabled (BOOLEAN, default 0)
|
||||
- portal_password_hash (TEXT, nullable)
|
||||
- portal_link_token (TEXT, nullable) [+ unique index]
|
||||
|
||||
Idempotent. Run once per existing DB:
|
||||
docker exec terra-view-terra-view-1 python3 backend/migrate_add_project_portal_auth.py
|
||||
"""
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
_COLUMNS = {
|
||||
"portal_enabled": "BOOLEAN DEFAULT 0",
|
||||
"portal_password_hash": "TEXT",
|
||||
"portal_link_token": "TEXT",
|
||||
}
|
||||
|
||||
|
||||
def migrate():
|
||||
possible_paths = [Path("data/seismo_fleet.db"), Path("data/sfm.db"), Path("data/seismo.db")]
|
||||
db_path = next((p for p in possible_paths if p.exists()), None)
|
||||
if db_path is None:
|
||||
print(f"Database not found in any of: {[str(p) for p in possible_paths]}")
|
||||
print("A fresh DB created via models.py will include these columns automatically.")
|
||||
return
|
||||
|
||||
print(f"Using database: {db_path}")
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("PRAGMA table_info(projects)")
|
||||
existing = {row[1] for row in cursor.fetchall()}
|
||||
|
||||
for col, ddl in _COLUMNS.items():
|
||||
if col in existing:
|
||||
print(f"○ Column already exists: projects.{col}")
|
||||
continue
|
||||
try:
|
||||
cursor.execute(f"ALTER TABLE projects ADD COLUMN {col} {ddl}")
|
||||
print(f"✓ Added column: projects.{col} ({ddl})")
|
||||
except sqlite3.OperationalError as e:
|
||||
print(f"✗ Failed to add projects.{col}: {e}")
|
||||
|
||||
# Unique index on the link token (separate from ADD COLUMN; idempotent via IF NOT EXISTS).
|
||||
try:
|
||||
cursor.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_projects_portal_link_token "
|
||||
"ON projects (portal_link_token)")
|
||||
print("✓ Ensured unique index: ix_projects_portal_link_token")
|
||||
except sqlite3.OperationalError as e:
|
||||
print(f"✗ Failed to create index: {e}")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("\n✓ Project portal-auth migration complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate()
|
||||
@@ -193,6 +193,10 @@ class Project(Base):
|
||||
# Project metadata
|
||||
client_name = Column(String, nullable=True, index=True) # Client name (e.g., "PJ Dick")
|
||||
client_id = Column(String, nullable=True, index=True) # FK -> clients.id; authoritative portal link (client_name kept for display)
|
||||
# --- Client portal (Phase 1: per-project link + password gate) ---
|
||||
portal_enabled = Column(Boolean, default=False) # is the portal open for this project
|
||||
portal_password_hash = Column(String, nullable=True) # argon2 hash of the shared password
|
||||
portal_link_token = Column(String, nullable=True, unique=True, index=True) # unguessable token in the secure link
|
||||
site_address = Column(String, nullable=True)
|
||||
site_coordinates = Column(String, nullable=True) # "lat,lon"
|
||||
start_date = Column(Date, nullable=True)
|
||||
|
||||
Reference in New Issue
Block a user