M9 S3 (backend): organize routes adopt toolkit + shared label reconciliation
DRY across the "organize/recall" backend surface:
- serialize.py (new): serialize_label(label) — the base {id,name,color}
shape. labels.py builds on it (adds count); sync deltas will (S4).
- labeling.py (new): resolve_owned_label_ids() + reconcile_manual_labels()
— the "set a note's MANUAL (picker) labels, leave the via_tag rows alone"
logic was duplicated line-for-line between notes.set_note_labels and
sync._apply_note_manual_labels. Now one home; both adopt it (removes the
redundant `chosen`==owned recompute in notes). Behavior-preserving.
- labels.py: json_error/not_found/parse_uuid, colors.normalize_color, and
serialize_label; dropped local LABEL_COLORS + _normalize_label_color
(NOTE_COLORS is the single palette) and `import uuid` (rule 22).
- saved_filters.py: json_error/not_found/parse_uuid for its 2 uuid parses
+ error shapes.
- graph.py: no change — no error/uuid/palette-normalize duplication to fold.
Test: DB-free test_serialize_label_shape guards the base shape.
sync.py's reconciliation swap is behavior-identical; operator-verified on
deploy (no Postgres CI lane).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
+4
-14
@@ -21,6 +21,7 @@ from sqlalchemy import func, select
|
||||
from .auth import login_required
|
||||
from .config import Config
|
||||
from .db import session_scope
|
||||
from .labeling import reconcile_manual_labels, resolve_owned_label_ids
|
||||
from .models.label import Label, NoteLabel
|
||||
from .models.note import Note
|
||||
from .models.note_attachment import NoteAttachment
|
||||
@@ -212,20 +213,9 @@ async def _apply_note_manual_labels(db, note: Note, ch: dict) -> None:
|
||||
try:
|
||||
wanted.add(uuid.UUID(str(r)))
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
owned: set = set()
|
||||
if wanted:
|
||||
owned = set(
|
||||
(await db.scalars(select(Label.id).where(Label.owner_id == g.user_id, Label.id.in_(wanted)))).all()
|
||||
)
|
||||
existing = (await db.scalars(select(NoteLabel).where(NoteLabel.note_id == note.id))).all()
|
||||
attached = {r.label_id for r in existing}
|
||||
for r in existing:
|
||||
if not r.via_tag and r.label_id not in owned:
|
||||
await db.delete(r)
|
||||
for lid in owned:
|
||||
if lid not in attached:
|
||||
db.add(NoteLabel(note_id=note.id, label_id=lid, via_tag=False))
|
||||
continue # sync is lenient: skip a malformed id rather than reject the push
|
||||
owned = await resolve_owned_label_ids(db, wanted, g.user_id)
|
||||
await reconcile_manual_labels(db, note, owned)
|
||||
|
||||
|
||||
async def _purge_note(db, note: Note, edited_at: datetime | None) -> None:
|
||||
|
||||
Reference in New Issue
Block a user