fix(dashboard): 54-44c 화면 취향 저장 때 지워진 프로젝트 id 키 버림 — 브라우저 옛 사본의 재오염 막음 · 시험 1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
This commit is contained in:
2026-10-06 15:27:32 +09:00
co-authored by Claude Opus 5.5
parent ca5ae1171e
commit 222a3cb1ff
2 changed files with 85 additions and 1 deletions
+15 -1
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import json
import re
import shutil
from datetime import datetime, timedelta
from typing import Any
@@ -547,10 +548,23 @@ async def get_user_ui_prefs(user_id: int) -> dict[str, Any]:
return raw if isinstance(raw, dict) else {}
_UUID_IN_KEY = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
async def save_user_ui_prefs(user_id: int, prefs: dict[str, Any]) -> None:
"""화면 취향을 통째로 덮어쓴다 — 사용자당 한 줄."""
"""화면 취향을 통째로 덮어쓴다 — 사용자당 한 줄.
지워진 프로젝트 id 가 박힌 키는 버림 — 브라우저 사본이 옛 키를 다시 올리지 못하게.
"""
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
keyed = {key: m.group(0) for key in prefs if (m := _UUID_IN_KEY.search(key))}
if keyed:
ids = sorted(set(keyed.values()))
holders = ", ".join(["%s"] * len(ids))
await cursor.execute(f"SELECT id FROM projects WHERE id IN ({holders})", ids)
live = {row[0] for row in await cursor.fetchall()}
prefs = {k: v for k, v in prefs.items() if k not in keyed or keyed[k] in live}
await cursor.execute(
"""INSERT INTO user_ui_prefs (user_id, prefs) VALUES (%s, %s)
ON DUPLICATE KEY UPDATE prefs = VALUES(prefs)""",
@@ -0,0 +1,70 @@
"""화면 취향 저장 — 지워진 프로젝트 id 가 박힌 키는 버림 · 나머지 키는 그대로."""
import asyncio
import json
import B01_Dashboard.B01_Dashboard_Repository as repo
LIVE = "226fc805-26c8-4f5a-9956-bacb200e10a5"
DEAD = "936be972-11bc-46c2-8bf3-b15d8de7df0d"
class _Cursor:
def __init__(self, store):
self.store = store
self.rows = []
async def __aenter__(self):
return self
async def __aexit__(self, *_):
return False
async def execute(self, sql, args):
if sql.startswith("SELECT id FROM projects"):
self.rows = [(i,) for i in args if i == LIVE]
else:
self.store["saved"] = json.loads(args[1])
async def fetchall(self):
return self.rows
class _Conn:
def __init__(self, store):
self.store = store
async def __aenter__(self):
return self
async def __aexit__(self, *_):
return False
def cursor(self):
return _Cursor(self.store)
async def commit(self):
pass
class _Pool:
def __init__(self, store):
self.store = store
def acquire(self):
return _Conn(self.store)
def test_dead_project_keys_dropped(monkeypatch):
store = {}
monkeypatch.setattr(repo, "get_db_pool", lambda: _Pool(store))
prefs = {
f"aislo:pref:routeedit-view:{LIVE}": "a",
f"aislo:pref:routeedit-view:{DEAD}": "b",
"aislo:pref:table-open": "true",
}
asyncio.run(repo.save_user_ui_prefs(3, prefs))
assert store["saved"] == {
f"aislo:pref:routeedit-view:{LIVE}": "a",
"aislo:pref:table-open": "true",
}