Files
pngx-sync/app/models.py
domverse cdc9407ff3
All checks were successful
Deploy / deploy (push) Successful in 33s
Implement master promotion feature
Allows promoting any replica to master with zero document re-downloads.
The sync_map rebuild uses existing DB data only — pure in-memory join.

Changes:
- app/sync/promote.py: preflight() checks (doc count, sync lock, ack
  warnings) and promote() transaction (pause scheduler, rebuild all
  sync_maps, create old-master replica, swap settings, resume scheduler)
- app/api/master.py: GET /api/master/promote/{id}/preflight (dry run)
  and POST /api/master/promote/{id} (execute)
- app/models.py: add promoted_from_master bool field to Replica
- app/database.py: idempotent ALTER TABLE migration for new column
- app/main.py: register master router
- app/templates/replica_detail.html: "Promote to Master" button +
  dialog with pre-flight summary, 3-card stats, ack checkboxes, spinner
- app/ui/routes.py: flash query param on dashboard route
- app/templates/dashboard.html: blue info banner for post-promotion flash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 21:17:01 +01:00

71 lines
2.3 KiB
Python

from datetime import datetime
from typing import Optional
from sqlalchemy import UniqueConstraint
from sqlmodel import Field, SQLModel
class Replica(SQLModel, table=True):
__tablename__ = "replicas" # type: ignore[assignment]
id: Optional[int] = Field(default=None, primary_key=True)
name: str
url: str
api_token: str # Fernet-encrypted
enabled: bool = True
sync_interval_seconds: Optional[int] = None
last_sync_ts: Optional[datetime] = None
consecutive_failures: int = 0
suspended_at: Optional[datetime] = None
last_alert_at: Optional[datetime] = None
promoted_from_master: Optional[bool] = Field(default=False)
created_at: datetime = Field(default_factory=datetime.utcnow)
class SyncMap(SQLModel, table=True):
__tablename__ = "sync_map" # type: ignore[assignment]
__table_args__ = (UniqueConstraint("replica_id", "master_doc_id"),) # type: ignore[assignment]
id: Optional[int] = Field(default=None, primary_key=True)
replica_id: int = Field(foreign_key="replicas.id")
master_doc_id: int
replica_doc_id: Optional[int] = None
task_id: Optional[str] = None
last_synced: Optional[datetime] = None
file_checksum: Optional[str] = None
status: str = "pending"
error_msg: Optional[str] = None
retry_count: int = 0
class SyncRun(SQLModel, table=True):
__tablename__ = "sync_runs" # type: ignore[assignment]
id: Optional[int] = Field(default=None, primary_key=True)
replica_id: Optional[int] = Field(default=None, foreign_key="replicas.id")
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
triggered_by: Optional[str] = None
docs_synced: int = 0
docs_failed: int = 0
timed_out: bool = False
class Log(SQLModel, table=True):
__tablename__ = "logs" # type: ignore[assignment]
id: Optional[int] = Field(default=None, primary_key=True)
run_id: Optional[int] = Field(default=None, foreign_key="sync_runs.id")
replica_id: Optional[int] = Field(default=None, foreign_key="replicas.id")
level: Optional[str] = None
message: Optional[str] = None
doc_id: Optional[int] = None
created_at: datetime = Field(default_factory=datetime.utcnow)
class Setting(SQLModel, table=True):
__tablename__ = "settings" # type: ignore[assignment]
key: str = Field(primary_key=True)
value: Optional[str] = None