Introduce reusable ParserProfile and ParseChannel, pair jobs with enqueue flatten, Events admin CRUD, and drop inline/legacy parser-builder UI and aliases. Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
3.3 KiB
Python
99 lines
3.3 KiB
Python
"""Admin CRUD for parse channels (Telegram handles in MVP)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..database import get_db
|
|
from ..models import ParseChannel, ParseJob
|
|
from ..schemas import ParseChannelCreate, ParseChannelRead, ParseChannelUpdate
|
|
|
|
router = APIRouter(prefix="/admin/parse-channels", tags=["parse-channels"])
|
|
|
|
ALLOWED_SOURCE_TYPES = {"telegram"}
|
|
|
|
|
|
@router.get("", response_model=list[ParseChannelRead])
|
|
def list_channels(db: Session = Depends(get_db)):
|
|
return db.query(ParseChannel).order_by(ParseChannel.id.desc()).all()
|
|
|
|
|
|
@router.post("", response_model=ParseChannelRead, status_code=201)
|
|
def create_channel(payload: ParseChannelCreate, db: Session = Depends(get_db)):
|
|
source_type = (payload.source_type or "telegram").strip()
|
|
if source_type not in ALLOWED_SOURCE_TYPES:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"source_type must be one of: {', '.join(sorted(ALLOWED_SOURCE_TYPES))}",
|
|
)
|
|
channel = ParseChannel(
|
|
name=payload.name.strip(),
|
|
source_type=source_type,
|
|
channel=payload.channel.strip().lstrip("@"),
|
|
is_active=payload.is_active,
|
|
)
|
|
db.add(channel)
|
|
db.commit()
|
|
db.refresh(channel)
|
|
return channel
|
|
|
|
|
|
@router.get("/{channel_id}", response_model=ParseChannelRead)
|
|
def get_channel(channel_id: int, db: Session = Depends(get_db)):
|
|
channel = db.query(ParseChannel).filter(ParseChannel.id == channel_id).first()
|
|
if not channel:
|
|
raise HTTPException(status_code=404, detail="Channel not found")
|
|
return channel
|
|
|
|
|
|
@router.patch("/{channel_id}", response_model=ParseChannelRead)
|
|
def update_channel(
|
|
channel_id: int,
|
|
payload: ParseChannelUpdate,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
channel = db.query(ParseChannel).filter(ParseChannel.id == channel_id).first()
|
|
if not channel:
|
|
raise HTTPException(status_code=404, detail="Channel not found")
|
|
|
|
if payload.name is not None:
|
|
channel.name = payload.name.strip()
|
|
if payload.source_type is not None:
|
|
source_type = payload.source_type.strip()
|
|
if source_type not in ALLOWED_SOURCE_TYPES:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"source_type must be one of: {', '.join(sorted(ALLOWED_SOURCE_TYPES))}",
|
|
)
|
|
channel.source_type = source_type
|
|
if payload.channel is not None:
|
|
channel.channel = payload.channel.strip().lstrip("@")
|
|
if payload.is_active is not None:
|
|
channel.is_active = payload.is_active
|
|
|
|
db.commit()
|
|
db.refresh(channel)
|
|
return channel
|
|
|
|
|
|
@router.delete("/{channel_id}", status_code=204)
|
|
def delete_channel(channel_id: int, db: Session = Depends(get_db)):
|
|
channel = db.query(ParseChannel).filter(ParseChannel.id == channel_id).first()
|
|
if not channel:
|
|
raise HTTPException(status_code=404, detail="Channel not found")
|
|
|
|
linked = (
|
|
db.query(ParseJob)
|
|
.filter(ParseJob.channel_id == channel_id, ParseJob.status.in_(("queued", "running")))
|
|
.count()
|
|
)
|
|
if linked:
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail="Cannot delete channel used by queued/running jobs",
|
|
)
|
|
|
|
db.delete(channel)
|
|
db.commit()
|