Moved channel URLs to channels_urls table. Create CRUD endpoints for new table.
All checks were successful
AWS Deploy on Push / build (push) Successful in 1m8s

This commit is contained in:
2025-05-23 11:36:04 -05:00
parent f11d533fac
commit c96ee307db
4 changed files with 142 additions and 13 deletions

View File

@@ -1,4 +1,4 @@
from .db import Base, ChannelDB from .db import Base, ChannelDB, ChannelURL
from .schemas import ChannelCreate, ChannelUpdate, ChannelResponse from .schemas import ChannelCreate, ChannelUpdate, ChannelResponse, ChannelURLCreate, ChannelURLResponse
__all__ = ["Base", "ChannelDB", "ChannelCreate", "ChannelUpdate", "ChannelResponse"] __all__ = ["Base", "ChannelDB", "ChannelCreate", "ChannelUpdate", "ChannelResponse", "ChannelURL", "ChannelURLCreate", "ChannelURLResponse"]

View File

@@ -1,8 +1,9 @@
from datetime import datetime, timezone from datetime import datetime, timezone
import uuid import uuid
from sqlalchemy import Column, String, JSON, DateTime, UniqueConstraint from sqlalchemy import Column, String, JSON, DateTime, UniqueConstraint, ForeignKey
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base() Base = declarative_base()
@@ -20,6 +21,21 @@ class ChannelDB(Base):
UniqueConstraint('group_title', 'name', name='uix_group_title_name'), UniqueConstraint('group_title', 'name', name='uix_group_title_name'),
) )
tvg_logo = Column(String) tvg_logo = Column(String)
urls = Column(JSON) # Stores list of URLs as JSON
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
# Relationship with ChannelURL
urls = relationship("ChannelURL", back_populates="channel", cascade="all, delete-orphan")
class ChannelURL(Base):
"""SQLAlchemy model for channel URLs"""
__tablename__ = "channels_urls"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
channel_id = Column(UUID(as_uuid=True), ForeignKey('channels.id', ondelete='CASCADE'), nullable=False)
url = Column(String, nullable=False)
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
# Relationship with ChannelDB
channel = relationship("ChannelDB", back_populates="urls")

View File

@@ -3,22 +3,49 @@ from typing import List
from uuid import UUID from uuid import UUID
from pydantic import BaseModel from pydantic import BaseModel
class ChannelURLCreate(BaseModel):
"""Pydantic model for creating channel URLs"""
url: str
class ChannelURLBase(ChannelURLCreate):
"""Base Pydantic model for channel URL responses"""
id: UUID
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class ChannelURLResponse(ChannelURLBase):
"""Pydantic model for channel URL responses"""
pass
class ChannelCreate(BaseModel): class ChannelCreate(BaseModel):
"""Pydantic model for creating channels""" """Pydantic model for creating channels"""
urls: List[str] urls: List[str] # List of URLs to create with the channel
name: str name: str
group_title: str group_title: str
tvg_id: str tvg_id: str
tvg_logo: str tvg_logo: str
tvg_name: str tvg_name: str
class ChannelUpdate(ChannelCreate): class ChannelUpdate(BaseModel):
"""Pydantic model for updating channels""" """Pydantic model for updating channels"""
pass name: str | None = None
group_title: str | None = None
tvg_id: str | None = None
tvg_logo: str | None = None
tvg_name: str | None = None
class ChannelResponse(ChannelCreate): class ChannelResponse(BaseModel):
"""Pydantic model for channel responses""" """Pydantic model for channel responses"""
id: UUID id: UUID
name: str
group_title: str
tvg_id: str
tvg_logo: str
tvg_name: str
urls: List[ChannelURLResponse] # List of URL objects without channel_id
created_at: datetime created_at: datetime
updated_at: datetime updated_at: datetime

View File

@@ -4,7 +4,15 @@ from typing import List
from uuid import UUID from uuid import UUID
from sqlalchemy import and_ from sqlalchemy import and_
from app.models import ChannelDB, ChannelCreate, ChannelUpdate, ChannelResponse from app.models import (
ChannelDB,
ChannelURL,
ChannelCreate,
ChannelUpdate,
ChannelResponse,
ChannelURLCreate,
ChannelURLResponse,
)
from app.utils.database import get_db from app.utils.database import get_db
from app.auth.dependencies import get_current_user, require_roles from app.auth.dependencies import get_current_user, require_roles
from app.models.auth import CognitoUser from app.models.auth import CognitoUser
@@ -36,10 +44,21 @@ def create_channel(
detail="Channel with same group_title and name already exists" detail="Channel with same group_title and name already exists"
) )
db_channel = ChannelDB(**channel.model_dump()) # Create channel without URLs first
channel_data = channel.model_dump()
urls = channel_data.pop('urls', [])
db_channel = ChannelDB(**channel_data)
db.add(db_channel) db.add(db_channel)
db.commit() db.commit()
db.refresh(db_channel) db.refresh(db_channel)
# Add URLs
for url in urls:
db_url = ChannelURL(channel_id=db_channel.id, url=url)
db.add(db_url)
db.commit()
db.refresh(db_channel)
return db_channel return db_channel
@router.get("/{channel_id}", response_model=ChannelResponse) @router.get("/{channel_id}", response_model=ChannelResponse)
@@ -121,4 +140,71 @@ def list_channels(
user: CognitoUser = Depends(get_current_user) user: CognitoUser = Depends(get_current_user)
): ):
"""List all channels with pagination""" """List all channels with pagination"""
return db.query(ChannelDB).offset(skip).limit(limit).all() return db.query(ChannelDB).offset(skip).limit(limit).all()
# URL Management Endpoints
@router.post("/{channel_id}/urls", response_model=ChannelURLResponse, status_code=status.HTTP_201_CREATED)
@require_roles("admin")
def add_channel_url(
channel_id: UUID,
url: ChannelURLCreate,
db: Session = Depends(get_db),
user: CognitoUser = Depends(get_current_user)
):
"""Add a new URL to a channel"""
channel = db.query(ChannelDB).filter(ChannelDB.id == channel_id).first()
if not channel:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Channel not found"
)
db_url = ChannelURL(channel_id=channel_id, url=url.url)
db.add(db_url)
db.commit()
db.refresh(db_url)
return db_url
@router.delete("/{channel_id}/urls/{url_id}", status_code=status.HTTP_204_NO_CONTENT)
@require_roles("admin")
def delete_channel_url(
channel_id: UUID,
url_id: UUID,
db: Session = Depends(get_db),
user: CognitoUser = Depends(get_current_user)
):
"""Delete a URL from a channel"""
url = db.query(ChannelURL).filter(
and_(
ChannelURL.id == url_id,
ChannelURL.channel_id == channel_id
)
).first()
if not url:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="URL not found"
)
db.delete(url)
db.commit()
return None
@router.get("/{channel_id}/urls", response_model=List[ChannelURLResponse])
@require_roles("admin")
def list_channel_urls(
channel_id: UUID,
db: Session = Depends(get_db),
user: CognitoUser = Depends(get_current_user)
):
"""List all URLs for a channel"""
channel = db.query(ChannelDB).filter(ChannelDB.id == channel_id).first()
if not channel:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Channel not found"
)
return db.query(ChannelURL).filter(ChannelURL.channel_id == channel_id).all()