168 lines
5.4 KiB
Python
168 lines
5.4 KiB
Python
import json
|
|
import sqlite3
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from uuid import UUID, uuid4
|
|
|
|
from app.database import get_connection
|
|
from app.models import Segment, SegmentCreateRequest, SegmentType, SegmentUpdateRequest
|
|
|
|
|
|
def _row_to_segment(row: sqlite3.Row) -> Segment:
|
|
return Segment(
|
|
id=UUID(row["id"]),
|
|
type=SegmentType(row["type"]),
|
|
sort_order=row["sort_order"],
|
|
title=row["title"],
|
|
content=row["content"],
|
|
metadata=json.loads(row["metadata"]),
|
|
page_id=dict(row).get("page_id"),
|
|
created_at=datetime.fromisoformat(row["created_at"]),
|
|
updated_at=datetime.fromisoformat(row["updated_at"]),
|
|
)
|
|
|
|
|
|
def create_segment(db_path: Path, request: SegmentCreateRequest) -> Segment:
|
|
conn = get_connection(db_path)
|
|
conn.isolation_level = None # manual transaction control
|
|
try:
|
|
conn.execute("BEGIN IMMEDIATE")
|
|
row = conn.execute(
|
|
"SELECT COALESCE(MAX(sort_order) + 1, 0) AS next_order"
|
|
" FROM segments WHERE page_id IS ?",
|
|
(request.page_id,),
|
|
).fetchone()
|
|
sort_order: int = row["next_order"] if row else 0
|
|
|
|
segment_id = uuid4()
|
|
now = datetime.now(UTC).isoformat()
|
|
metadata_json = json.dumps(request.metadata)
|
|
|
|
cols = "id, type, sort_order, title, content, metadata, page_id, created_at, updated_at"
|
|
conn.execute(
|
|
f"INSERT INTO segments ({cols}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
(
|
|
str(segment_id),
|
|
request.type.value,
|
|
sort_order,
|
|
request.title,
|
|
request.content,
|
|
metadata_json,
|
|
request.page_id,
|
|
now,
|
|
now,
|
|
),
|
|
)
|
|
conn.execute("COMMIT")
|
|
|
|
return Segment(
|
|
id=segment_id,
|
|
type=request.type,
|
|
sort_order=sort_order,
|
|
title=request.title,
|
|
content=request.content,
|
|
metadata=request.metadata,
|
|
page_id=request.page_id,
|
|
created_at=datetime.fromisoformat(now),
|
|
updated_at=datetime.fromisoformat(now),
|
|
)
|
|
except Exception:
|
|
conn.execute("ROLLBACK")
|
|
raise
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def list_segments(db_path: Path, page_id: str | None = None) -> list[Segment]:
|
|
conn = get_connection(db_path)
|
|
try:
|
|
if page_id is not None:
|
|
rows = conn.execute(
|
|
"SELECT * FROM segments WHERE page_id = ? ORDER BY sort_order ASC",
|
|
(page_id,),
|
|
).fetchall()
|
|
else:
|
|
rows = conn.execute("SELECT * FROM segments ORDER BY sort_order ASC").fetchall()
|
|
return [_row_to_segment(row) for row in rows]
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def get_segment(db_path: Path, segment_id: UUID) -> Segment | None:
|
|
conn = get_connection(db_path)
|
|
try:
|
|
row = conn.execute("SELECT * FROM segments WHERE id = ?", (str(segment_id),)).fetchone()
|
|
if row is None:
|
|
return None
|
|
return _row_to_segment(row)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def update_segment(
|
|
db_path: Path, segment_id: UUID, request: SegmentUpdateRequest
|
|
) -> Segment | None:
|
|
conn = get_connection(db_path)
|
|
try:
|
|
existing = conn.execute(
|
|
"SELECT * FROM segments WHERE id = ?", (str(segment_id),)
|
|
).fetchone()
|
|
if existing is None:
|
|
return None
|
|
|
|
now = datetime.now(UTC).isoformat()
|
|
title = request.title if request.title is not None else existing["title"]
|
|
content = request.content if request.content is not None else existing["content"]
|
|
metadata_json = (
|
|
json.dumps(request.metadata) if request.metadata is not None else existing["metadata"]
|
|
)
|
|
|
|
conn.execute(
|
|
"""UPDATE segments SET title = ?, content = ?, metadata = ?, updated_at = ?
|
|
WHERE id = ?""",
|
|
(title, content, metadata_json, now, str(segment_id)),
|
|
)
|
|
conn.commit()
|
|
|
|
return get_segment(db_path, segment_id)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def delete_segment(db_path: Path, segment_id: UUID) -> bool:
|
|
conn = get_connection(db_path)
|
|
try:
|
|
cursor = conn.execute("DELETE FROM segments WHERE id = ?", (str(segment_id),))
|
|
conn.commit()
|
|
return cursor.rowcount > 0
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def reorder_segments(db_path: Path, segment_ids: list[UUID]) -> list[Segment]:
|
|
conn = get_connection(db_path)
|
|
try:
|
|
existing_ids = {row["id"] for row in conn.execute("SELECT id FROM segments").fetchall()}
|
|
requested_ids = {str(sid) for sid in segment_ids}
|
|
missing = requested_ids - existing_ids
|
|
if missing:
|
|
raise ValueError(f"Segment IDs not found: {missing}")
|
|
|
|
now = datetime.now(UTC).isoformat()
|
|
for index, sid in enumerate(segment_ids):
|
|
conn.execute(
|
|
"UPDATE segments SET sort_order = ?, updated_at = ? WHERE id = ?",
|
|
(index, now, str(sid)),
|
|
)
|
|
conn.commit()
|
|
|
|
# Return segments scoped to the same page as the first segment in the list
|
|
if segment_ids:
|
|
row = conn.execute(
|
|
"SELECT page_id FROM segments WHERE id = ?", (str(segment_ids[0]),)
|
|
).fetchone()
|
|
page_id = row["page_id"] if row else None
|
|
return list_segments(db_path, page_id=page_id)
|
|
return []
|
|
finally:
|
|
conn.close()
|