Files
pngx-sync/app/models.py
domverse b99dbf694d
All checks were successful
Deploy / deploy (push) Successful in 30s
feat: implement pngx-controller with Gitea CI/CD deployment
- Full FastAPI sync engine: master→replica document sync via paperless REST API
- Web UI: dashboard, replicas, logs, settings (Jinja2 + HTMX + Pico CSS)
- APScheduler background sync, SSE live log stream, Prometheus metrics
- Fernet encryption for API tokens at rest
- pngx.env credential file: written on save, pre-fills forms on load
- Dockerfile with layer-cached uv build, Python healthcheck
- docker-compose with host networking for Tailscale access
- Gitea Actions workflow: version bump, secret injection, docker compose deploy

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

70 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
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