diff --git a/linedance-api/app/routers/sync.py b/linedance-api/app/routers/sync.py index 618c911c..78af50d3 100644 --- a/linedance-api/app/routers/sync.py +++ b/linedance-api/app/routers/sync.py @@ -76,7 +76,7 @@ class PushPayload(BaseModel): song_dances: list[SongDanceData] = [] song_alts: list[SongAltDanceData] = [] playlists: list[PlaylistData] = [] - deleted_playlists: list[str] = [] # navne på slettede playlister + deleted_playlists: list[str] = [] # server-IDs (api_project_id) på slettede playlister # ── Push ────────────────────────────────────────────────────────────────────── @@ -241,8 +241,9 @@ def push( db.add(proj_song) # ── Slet playlister der er fjernet lokalt ───────────────────────────────── - for name in payload.deleted_playlists: - proj = db.query(Project).filter_by(owner_id=me.id, name=name).first() + # Klienten sender api_project_id (= server Project.id) som strings + for project_id in payload.deleted_playlists: + proj = db.query(Project).filter_by(id=project_id, owner_id=me.id).first() if proj: db.query(ProjectSong).filter_by(project_id=proj.id).delete() db.delete(proj) @@ -378,4 +379,4 @@ def pull( "shared": shared, "my_playlists": my_playlists, "song_tags": song_tags, - } + } \ No newline at end of file diff --git a/linedance-app/local/local_db.py b/linedance-app/local/local_db.py index 1a201690..cb63f989 100644 --- a/linedance-app/local/local_db.py +++ b/linedance-app/local/local_db.py @@ -304,13 +304,16 @@ MIGRATIONS: dict[int, list[str]] = { 12: [ # Tabel til at huske slettede playlister — til sync med serveren """CREATE TABLE IF NOT EXISTS deleted_playlists ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - deleted_at TEXT NOT NULL DEFAULT (datetime('now')) + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + deleted_at TEXT NOT NULL DEFAULT (datetime('now')) )""", ], 13: [ - # Tilføj api_project_id så serveren præcist ved hvilken playlist der skal slettes + # Soft delete: is_deleted=1 i stedet for at slette rækken. + # Giver sync mulighed for at sende sletningen til serveren. + """ALTER TABLE playlists ADD COLUMN is_deleted INTEGER NOT NULL DEFAULT 0""", + # api_project_id kolonne på deleted_playlists (hvis den mangler fra migration 12) """ALTER TABLE deleted_playlists ADD COLUMN api_project_id TEXT""", ], } @@ -550,24 +553,6 @@ def create_playlist(name: str, description: str = "", tags: str = "") -> int: return cur.lastrowid -def delete_playlist(playlist_id: int): - """ - Slet en playliste lokalt og registrér sletningen til næste sync. - Gemmer api_project_id så serveren præcist ved hvad der skal slettes. - """ - with get_db() as conn: - row = conn.execute( - "SELECT name, api_project_id FROM playlists WHERE id=?", - (playlist_id,) - ).fetchone() - if row: - conn.execute( - "INSERT INTO deleted_playlists (name, api_project_id) VALUES (?,?)", - (row["name"], row["api_project_id"] or None) - ) - conn.execute("DELETE FROM playlists WHERE id=?", (playlist_id,)) - - def create_linked_playlist(name: str, api_project_id: str, permission: str = "view", description: str = "", tags: str = "") -> int: @@ -614,7 +599,7 @@ def get_playlists(tag_filter: str | None = None) -> list[sqlite3.Row]: SELECT p.*, COUNT(ps.id) as song_count FROM playlists p LEFT JOIN playlist_songs ps ON ps.playlist_id = p.id - WHERE p.name != ? AND ( + WHERE p.name != ? AND p.is_deleted = 0 AND ( p.tags LIKE ? OR p.tags LIKE ? OR p.tags LIKE ? OR p.tags = ? ) @@ -632,13 +617,27 @@ def get_playlists(tag_filter: str | None = None) -> list[sqlite3.Row]: SELECT p.*, COUNT(ps.id) as song_count FROM playlists p LEFT JOIN playlist_songs ps ON ps.playlist_id = p.id - WHERE p.name != ? + WHERE p.name != ? AND p.is_deleted = 0 GROUP BY p.id ORDER BY p.created_at DESC """, ("__aktiv__",)).fetchall() return rows +def delete_playlist(playlist_id: int): + """ + Soft-slet en playliste: sæt is_deleted=1 i stedet for at fjerne rækken. + Sync kan derefter sende sletningen til serveren via api_project_id. + """ + with get_db() as conn: + conn.execute( + "UPDATE playlists SET is_deleted=1 WHERE id=?", + (playlist_id,) + ) + + + + def add_song_to_playlist(playlist_id: int, song_id: str, position: int | None = None) -> int: with get_db() as conn: if position is None: @@ -895,6 +894,4 @@ def get_dance_name_suggestions(prefix: str, limit: int = 20) -> list[str]: result.append(f"{s['name']} / {s['level_name']}") else: result.append(s["name"]) - return result - - + return result \ No newline at end of file diff --git a/linedance-app/local/sync_manager.py b/linedance-app/local/sync_manager.py index 22f2ef50..d60ca673 100644 --- a/linedance-app/local/sync_manager.py +++ b/linedance-app/local/sync_manager.py @@ -57,10 +57,12 @@ class SyncManager: logger.info(f"Push OK: {len(payload['songs'])} sange") result = self._post("/sync/push", payload) self._save_playlist_ids(result.get("playlist_id_map", {})) - # Ryd deleted_playlists nu de er sendt til serveren + # Fjern soft-slettede playlister permanent efter succesfuld push if payload.get("deleted_playlists"): conn = sqlite3.connect(self._db_path) - conn.execute("DELETE FROM deleted_playlists") + conn.execute( + "DELETE FROM playlists WHERE is_deleted=1 AND api_project_id IS NOT NULL" + ) conn.commit() conn.close() logger.info(f"Push OK: {result.get('songs_synced', '?')} sange synkroniseret") @@ -116,23 +118,26 @@ class SyncManager: f"sletter {len(deleted)}: {deleted}") push_result = self._post("/sync/push", payload) self._save_playlist_ids(push_result.get("playlist_id_map", {})) - logger.info(f"Push svar: {push_result}") + logger.info(f"Push svar: status={push_result.get('status')}, " + f"sange={push_result.get('songs_synced', 0)}, " + f"playlister={push_result.get('playlists_synced', 0)}") # 2. Pull — sletninger er nu gennemført på serveren. - # VIGTIGT: ryd deleted_playlists EFTER pull, så _apply_pull - # stadig kan filtrere de slettede lister fra. + # _apply_pull filtrerer is_deleted=1 rækker fra automatisk. pull_result = self._get("/sync/pull") pl_names = [p.get("name") for p in pull_result.get("my_playlists", [])] logger.info(f"Pull modtog {len(pl_names)} playlister: {pl_names}") self._apply_pull(pull_result) - # Ryd deleted_playlists nu pull er kørt og filtreringen er sket + # Fjern soft-slettede playlister permanent nu serveren er opdateret if deleted: conn = sqlite3.connect(self._db_path) - conn.execute("DELETE FROM deleted_playlists") + conn.execute( + "DELETE FROM playlists WHERE is_deleted=1 AND api_project_id IS NOT NULL" + ) conn.commit() conn.close() - logger.info(f"deleted_playlists ryddet efter pull") + logger.info(f"Soft-slettede playlister fjernet lokalt efter sync") pl_count = len(pull_result.get("my_playlists", [])) logger.info( @@ -252,13 +257,13 @@ class SyncManager: # Slettede playlister — skal fjernes fra serveren. # Serveren forventer en liste af strings (api_project_id). - # Playlister uden api_project_id har aldrig nået serveren — ignorer dem. + # Kun playlister der faktisk er nået serveren (har api_project_id). deleted = [ row["api_project_id"] for row in conn.execute( - "SELECT api_project_id FROM deleted_playlists" + "SELECT api_project_id FROM playlists " + "WHERE is_deleted=1 AND api_project_id IS NOT NULL AND api_project_id != ''" ).fetchall() - if row["api_project_id"] ] conn.close() @@ -296,19 +301,14 @@ class SyncManager: d.get("stepsheet_url",""), existing["id"])) # Importer/opdater egne playlister fra server — server er sandhed - # Hent både navne og server-IDs på lokalt slettede playlister - try: - deleted_rows = conn.execute( - "SELECT name, api_project_id FROM deleted_playlists" + # Hent server-IDs på soft-slettede playlister så vi springer dem over + deleted_server_ids = { + row["api_project_id"] + for row in conn.execute( + "SELECT api_project_id FROM playlists " + "WHERE is_deleted=1 AND api_project_id IS NOT NULL" ).fetchall() - deleted_server_ids = { - row["api_project_id"] for row in deleted_rows - if row["api_project_id"] - } - deleted_names = {row["name"] for row in deleted_rows} - except Exception: - deleted_server_ids = set() - deleted_names = set() + } for pl in data.get("my_playlists", []): server_id = pl.get("server_id") @@ -316,12 +316,9 @@ class SyncManager: if not server_id or not name: continue - # Spring over hvis listen er markeret som slettet lokalt - # Tjek på server-ID (præcist) og navn (fallback) + # Spring over hvis listen er soft-slettet lokalt if server_id in deleted_server_ids: continue - if name in deleted_names and not server_id: - continue existing = conn.execute( "SELECT id FROM playlists WHERE api_project_id=?", (server_id,)