Разделы, фильтры и просмотр текстов в UI; опция «не хранить видео»
- таблица sections (дерево через parent_id), у видео section_id
- API: CRUD /sections, фильтры /videos (поиск, раздел с потомками, метод),
GET /videos/{id}/text, PATCH раздела, DELETE вместе с файлами
- pipeline: keep_video=false удаляет mp4 после расшифровки, section_id
- UI: панель разделов с вложенностью и счётчиками, поиск, фильтр по методу,
диалоги полного текста/выжимки, привязка к разделу, удаление,
переключатель «Сохранять видео на диске»
- compose: bind-mount ./web для правок без пересборки
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
57
api/db/section.py
Normal file
57
api/db/section.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from sqlalchemy import func, select, update
|
||||
|
||||
from api.db.connection import SessionLocal
|
||||
from api.models.section import Section
|
||||
from api.models.video import Video
|
||||
|
||||
|
||||
async def list_sections() -> list[dict]:
|
||||
"""Плоский список разделов + количество видео в каждом (дерево строит клиент)."""
|
||||
async with SessionLocal() as session:
|
||||
res = await session.execute(select(Section).order_by(Section.name))
|
||||
sections = list(res.scalars().all())
|
||||
counts_res = await session.execute(
|
||||
select(Video.section_id, func.count()).group_by(Video.section_id)
|
||||
)
|
||||
counts = dict(counts_res.all())
|
||||
return [
|
||||
{
|
||||
"id": s.id,
|
||||
"name": s.name,
|
||||
"parent_id": s.parent_id,
|
||||
"video_count": counts.get(s.id, 0),
|
||||
}
|
||||
for s in sections
|
||||
]
|
||||
|
||||
|
||||
async def create_section(name: str, parent_id: int | None) -> Section:
|
||||
async with SessionLocal() as session:
|
||||
if parent_id is not None:
|
||||
parent = await session.get(Section, parent_id)
|
||||
if parent is None:
|
||||
raise ValueError(f"Parent section {parent_id} not found")
|
||||
s = Section(name=name, parent_id=parent_id)
|
||||
session.add(s)
|
||||
await session.commit()
|
||||
await session.refresh(s)
|
||||
return s
|
||||
|
||||
|
||||
async def delete_section(section_id: int) -> bool:
|
||||
"""Удаляет раздел: дочерние разделы поднимаются к родителю, видео — без раздела."""
|
||||
async with SessionLocal() as session:
|
||||
s = await session.get(Section, section_id)
|
||||
if s is None:
|
||||
return False
|
||||
await session.execute(
|
||||
update(Section)
|
||||
.where(Section.parent_id == section_id)
|
||||
.values(parent_id=s.parent_id)
|
||||
)
|
||||
await session.execute(
|
||||
update(Video).where(Video.section_id == section_id).values(section_id=None)
|
||||
)
|
||||
await session.delete(s)
|
||||
await session.commit()
|
||||
return True
|
||||
Reference in New Issue
Block a user