Introduced groups and added all related endpoints
All checks were successful
AWS Deploy on Push / build (push) Successful in 7m39s

This commit is contained in:
2025-06-10 23:02:46 -05:00
parent 729eabf27f
commit b8ac25e301
15 changed files with 1563 additions and 213 deletions

View File

@@ -1,10 +1,13 @@
from .db import Base, ChannelDB, ChannelURL
from .db import Base, ChannelDB, ChannelURL, Group
from .schemas import (
ChannelCreate,
ChannelResponse,
ChannelUpdate,
ChannelURLCreate,
ChannelURLResponse,
GroupCreate,
GroupResponse,
GroupUpdate,
)
__all__ = [
@@ -16,4 +19,8 @@ __all__ = [
"ChannelURL",
"ChannelURLCreate",
"ChannelURLResponse",
"Group",
"GroupCreate",
"GroupResponse",
"GroupUpdate",
]

View File

@@ -1,18 +1,58 @@
import os
import uuid
from datetime import datetime, timezone
from sqlalchemy import (
TEXT,
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
String,
TypeDecorator,
UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import declarative_base, relationship
# Custom UUID type for SQLite compatibility
class SQLiteUUID(TypeDecorator):
"""Enables UUID support for SQLite with proper comparison handling."""
impl = TEXT
cache_ok = True
def process_bind_param(self, value, dialect):
if value is None:
return value
if isinstance(value, uuid.UUID):
return str(value)
try:
# Validate string format by attempting to create UUID
uuid.UUID(value)
return value
except (ValueError, AttributeError):
raise ValueError(f"Invalid UUID string format: {value}")
def process_result_value(self, value, dialect):
if value is None:
return value
return uuid.UUID(value)
def compare_values(self, x, y):
if x is None or y is None:
return x == y
return str(x) == str(y)
# Determine which UUID type to use based on environment
if os.getenv("MOCK_AUTH", "").lower() == "true":
UUID_COLUMN_TYPE = SQLiteUUID()
else:
UUID_COLUMN_TYPE = UUID(as_uuid=True)
Base = declarative_base()
@@ -25,20 +65,37 @@ class Priority(Base):
description = Column(String, nullable=False)
class Group(Base):
"""SQLAlchemy model for channel groups"""
__tablename__ = "groups"
id = Column(UUID_COLUMN_TYPE, primary_key=True, default=uuid.uuid4)
name = Column(String, nullable=False, unique=True)
sort_order = Column(Integer, nullable=False, default=0)
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 Channel
channels = relationship("ChannelDB", back_populates="group")
class ChannelDB(Base):
"""SQLAlchemy model for IPTV channels"""
__tablename__ = "channels"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
id = Column(UUID_COLUMN_TYPE, primary_key=True, default=uuid.uuid4)
tvg_id = Column(String, nullable=False)
name = Column(String, nullable=False)
group_title = Column(String, nullable=False)
group_id = Column(UUID_COLUMN_TYPE, ForeignKey("groups.id"), nullable=False)
tvg_name = Column(String)
__table_args__ = (
UniqueConstraint("group_title", "name", name="uix_group_title_name"),
)
__table_args__ = (UniqueConstraint("group_id", "name", name="uix_group_id_name"),)
tvg_logo = Column(String)
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = Column(
@@ -47,10 +104,11 @@ class ChannelDB(Base):
onupdate=lambda: datetime.now(timezone.utc),
)
# Relationship with ChannelURL
# Relationships
urls = relationship(
"ChannelURL", back_populates="channel", cascade="all, delete-orphan"
)
group = relationship("Group", back_populates="channels")
class ChannelURL(Base):
@@ -58,9 +116,9 @@ class ChannelURL(Base):
__tablename__ = "channels_urls"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
id = Column(UUID_COLUMN_TYPE, primary_key=True, default=uuid.uuid4)
channel_id = Column(
UUID(as_uuid=True),
UUID_COLUMN_TYPE,
ForeignKey("channels.id", ondelete="CASCADE"),
nullable=False,
)

View File

@@ -53,12 +53,54 @@ class ChannelURLResponse(ChannelURLBase):
pass
# New Group Schemas
class GroupCreate(BaseModel):
"""Pydantic model for creating groups"""
name: str
sort_order: int = Field(default=0, ge=0)
class GroupUpdate(BaseModel):
"""Pydantic model for updating groups"""
name: Optional[str] = None
sort_order: Optional[int] = Field(None, ge=0)
class GroupResponse(BaseModel):
"""Pydantic model for group responses"""
id: UUID
name: str
sort_order: int
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class GroupSortUpdate(BaseModel):
"""Pydantic model for updating a single group's sort order"""
sort_order: int = Field(ge=0)
class GroupBulkSort(BaseModel):
"""Pydantic model for bulk updating group sort orders"""
groups: list[dict] = Field(
description="List of dicts with group_id and new sort_order",
json_schema_extra={"example": [{"group_id": "uuid", "sort_order": 1}]},
)
class ChannelCreate(BaseModel):
"""Pydantic model for creating channels"""
urls: list[ChannelURLCreate] # List of URL objects with priority
name: str
group_title: str
group_id: UUID
tvg_id: str
tvg_logo: str
tvg_name: str
@@ -76,7 +118,7 @@ class ChannelUpdate(BaseModel):
"""Pydantic model for updating channels (all fields optional)"""
name: Optional[str] = Field(None, min_length=1)
group_title: Optional[str] = Field(None, min_length=1)
group_id: Optional[UUID] = None
tvg_id: Optional[str] = Field(None, min_length=1)
tvg_logo: Optional[str] = None
tvg_name: Optional[str] = Field(None, min_length=1)
@@ -87,7 +129,7 @@ class ChannelResponse(BaseModel):
id: UUID
name: str
group_title: str
group_id: UUID
tvg_id: str
tvg_logo: str
tvg_name: str