feat: add atomic state management (TripState, Photo, Group)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 15:51:26 +02:00
parent 7ce02d642a
commit 102ad7b77b
2 changed files with 120 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
import json
import os
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Optional
from flask import current_app
@dataclass
class Photo:
id: str
original_filename: str
local_datetime: str
tag: str = "untagged" # untagged | journal | story | skip
order: int = 0
@dataclass
class Group:
id: str
photo_ids: list = field(default_factory=list)
entry_type: str = "journal" # journal | story
title: str = ""
body: str = ""
location_city: str = ""
location_country: str = ""
date: str = ""
hero_photo_id: Optional[str] = None
shortcode_hints: str = ""
status: str = "draft" # draft | written | skipped | exported
@dataclass
class TripState:
album_id: str
album_name: str
grav_trip_slug: str
phase: str = "triage"
phases_completed: list = field(default_factory=list)
phase_stale: list = field(default_factory=list)
photos: list = field(default_factory=list)
groups: list = field(default_factory=list)
notes: str = ""
def _state_path(album_id: str, app) -> Path:
return Path(app.config["STATE_DIR"]) / f"{album_id}.json"
def load_state(album_id: str, app) -> Optional[TripState]:
path = _state_path(album_id, app)
if not path.exists():
return None
with open(path) as f:
data = json.load(f)
photos = [Photo(**p) for p in data.pop("photos", [])]
groups = [Group(**g) for g in data.pop("groups", [])]
return TripState(photos=photos, groups=groups, **data)
def save_state(state: TripState, app) -> None:
path = _state_path(state.album_id, app)
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_suffix(".tmp")
with open(tmp, "w") as f:
json.dump(asdict(state), f, indent=2)
os.rename(tmp, path)