Compose and supporting code for four services that had been running or prototyped without their config tracked here, per the repo convention that agap_git holds the compose + config while application source lives in each service's own Gitea repo. Only placeholder credentials are included: mood/.env.example and moodtracker/.env.example ship dummy values, and overleaf/variables.env carries app name and feature flags only. Real values stay in Vaultwarden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
51 lines
1.9 KiB
Python
51 lines
1.9 KiB
Python
"""Sync orchestration: read moodtracker's SQLite file -> idempotent upsert.
|
|
|
|
Single stream ('moodtracker_entries'), cursor = highest source_id ingested so
|
|
far. Each run re-checks a small overlap of already-synced ids (config.OVERLAP_ROWS)
|
|
as a cheap safety net, then upserts anything with id > cursor - overlap.
|
|
"""
|
|
import time
|
|
|
|
from . import config, store
|
|
from .mood_source import connect_source, fetch_entries_since
|
|
|
|
STREAM_KEY = "moodtracker_entries"
|
|
|
|
|
|
def run_sync(conn, source_db_path=None):
|
|
"""One sync pass. Returns a counts dict. Never raises — errors are
|
|
recorded in ingest_runs/sync_state and returned in totals['errors']."""
|
|
source_db_path = source_db_path or config.SOURCE_DB_PATH
|
|
run_id = store.start_run(conn)
|
|
totals = {"entries": 0, "errors": []}
|
|
try:
|
|
last = store.get_last_synced_id(conn, STREAM_KEY)
|
|
since = max(0, last - config.OVERLAP_ROWS)
|
|
source_conn = connect_source(source_db_path)
|
|
try:
|
|
rows = fetch_entries_since(source_conn, since)
|
|
finally:
|
|
source_conn.close()
|
|
n = store.upsert_entries(conn, rows)
|
|
totals["entries"] = n
|
|
max_id = max((r["id"] for r in rows), default=last)
|
|
store.set_sync_state(conn, STREAM_KEY, max(max_id, last))
|
|
store.finish_run(conn, run_id, "ok", entries=n)
|
|
except Exception as e: # noqa: BLE001 - isolate failures, keep the loop alive
|
|
totals["errors"].append(str(e))
|
|
store.set_sync_state(
|
|
conn, STREAM_KEY, store.get_last_synced_id(conn, STREAM_KEY),
|
|
status="error", error=str(e),
|
|
)
|
|
store.finish_run(conn, run_id, "error", entries=0, error=str(e))
|
|
return totals
|
|
|
|
|
|
def sync_loop(conn, source_db_path=None, interval=None, once=False):
|
|
interval = interval or config.SYNC_INTERVAL_SECONDS
|
|
while True:
|
|
yield run_sync(conn, source_db_path)
|
|
if once:
|
|
return
|
|
time.sleep(interval)
|