Added in_use and priority_id field for channels urls. Added priorities table. Setup sql alchemy migration. Generate first migration.
All checks were successful
AWS Deploy on Push / build (push) Successful in 2m4s
All checks were successful
AWS Deploy on Push / build (push) Successful in 2m4s
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
|
||||
from app.routers import channels, auth, playlist
|
||||
from app.routers import channels, auth, playlist, priorities
|
||||
from fastapi import FastAPI
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
@@ -55,4 +55,5 @@ async def root():
|
||||
# Include routers
|
||||
app.include_router(auth.router)
|
||||
app.include_router(channels.router)
|
||||
app.include_router(playlist.router)
|
||||
app.include_router(playlist.router)
|
||||
app.include_router(priorities.router)
|
||||
@@ -1,12 +1,19 @@
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, JSON, DateTime, UniqueConstraint, ForeignKey
|
||||
from sqlalchemy import Column, String, JSON, DateTime, UniqueConstraint, ForeignKey, Boolean, Integer
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Priority(Base):
|
||||
"""SQLAlchemy model for channel URL priorities"""
|
||||
__tablename__ = "priorities"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
description = Column(String, nullable=False)
|
||||
|
||||
class ChannelDB(Base):
|
||||
"""SQLAlchemy model for IPTV channels"""
|
||||
__tablename__ = "channels"
|
||||
@@ -34,8 +41,11 @@ class ChannelURL(Base):
|
||||
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)
|
||||
in_use = Column(Boolean, default=False, nullable=False)
|
||||
priority_id = Column(Integer, ForeignKey('priorities.id'), 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")
|
||||
# Relationships
|
||||
channel = relationship("ChannelDB", back_populates="urls")
|
||||
priority = relationship("Priority")
|
||||
@@ -1,17 +1,36 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class PriorityBase(BaseModel):
|
||||
"""Base Pydantic model for priorities"""
|
||||
id: int
|
||||
description: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class PriorityCreate(PriorityBase):
|
||||
"""Pydantic model for creating priorities"""
|
||||
pass
|
||||
|
||||
class PriorityResponse(PriorityBase):
|
||||
"""Pydantic model for priority responses"""
|
||||
pass
|
||||
|
||||
class ChannelURLCreate(BaseModel):
|
||||
"""Pydantic model for creating channel URLs"""
|
||||
url: str
|
||||
priority_id: int = Field(default=100, ge=100, le=300) # Default to High, validate range
|
||||
|
||||
class ChannelURLBase(ChannelURLCreate):
|
||||
"""Base Pydantic model for channel URL responses"""
|
||||
id: UUID
|
||||
in_use: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
priority_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -22,20 +41,26 @@ class ChannelURLResponse(ChannelURLBase):
|
||||
|
||||
class ChannelCreate(BaseModel):
|
||||
"""Pydantic model for creating channels"""
|
||||
urls: List[str] # List of URLs to create with the channel
|
||||
urls: List[ChannelURLCreate] # List of URL objects with priority
|
||||
name: str
|
||||
group_title: str
|
||||
tvg_id: str
|
||||
tvg_logo: str
|
||||
tvg_name: str
|
||||
|
||||
class ChannelURLUpdate(BaseModel):
|
||||
"""Pydantic model for updating channel URLs"""
|
||||
url: Optional[str] = None
|
||||
in_use: Optional[bool] = None
|
||||
priority_id: Optional[int] = Field(default=None, ge=100, le=300)
|
||||
|
||||
class ChannelUpdate(BaseModel):
|
||||
"""Pydantic model for updating channels"""
|
||||
name: Optional[str] = None
|
||||
group_title: Optional[str] = None
|
||||
tvg_id: Optional[str] = None
|
||||
"""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)
|
||||
tvg_id: Optional[str] = Field(None, min_length=1)
|
||||
tvg_logo: Optional[str] = None
|
||||
tvg_name: Optional[str] = None
|
||||
tvg_name: Optional[str] = Field(None, min_length=1)
|
||||
|
||||
class ChannelResponse(BaseModel):
|
||||
"""Pydantic model for channel responses"""
|
||||
|
||||
@@ -13,6 +13,7 @@ from app.models import (
|
||||
ChannelURLCreate,
|
||||
ChannelURLResponse,
|
||||
)
|
||||
from app.models.schemas import ChannelURLUpdate
|
||||
from app.utils.database import get_db
|
||||
from app.auth.dependencies import get_current_user, require_roles
|
||||
from app.models.auth import CognitoUser
|
||||
@@ -45,16 +46,21 @@ def create_channel(
|
||||
)
|
||||
|
||||
# Create channel without URLs first
|
||||
channel_data = channel.model_dump()
|
||||
urls = channel_data.pop('urls', [])
|
||||
channel_data = channel.model_dump(exclude={'urls'})
|
||||
urls = channel.urls
|
||||
db_channel = ChannelDB(**channel_data)
|
||||
db.add(db_channel)
|
||||
db.commit()
|
||||
db.refresh(db_channel)
|
||||
|
||||
# Add URLs
|
||||
# Add URLs with priority
|
||||
for url in urls:
|
||||
db_url = ChannelURL(channel_id=db_channel.id, url=url)
|
||||
db_url = ChannelURL(
|
||||
channel_id=db_channel.id,
|
||||
url=url.url,
|
||||
priority_id=url.priority_id,
|
||||
in_use=False
|
||||
)
|
||||
db.add(db_url)
|
||||
|
||||
db.commit()
|
||||
@@ -91,22 +97,28 @@ def update_channel(
|
||||
detail="Channel not found"
|
||||
)
|
||||
|
||||
# Check for duplicate channel (same group_title + name) excluding current channel
|
||||
existing_channel = db.query(ChannelDB).filter(
|
||||
and_(
|
||||
ChannelDB.group_title == channel.group_title,
|
||||
ChannelDB.name == channel.name,
|
||||
ChannelDB.id != channel_id
|
||||
)
|
||||
).first()
|
||||
# Only check for duplicates if name or group_title are being updated
|
||||
if channel.name is not None or channel.group_title is not None:
|
||||
name = channel.name if channel.name is not None else db_channel.name
|
||||
group_title = channel.group_title if channel.group_title is not None else db_channel.group_title
|
||||
|
||||
existing_channel = db.query(ChannelDB).filter(
|
||||
and_(
|
||||
ChannelDB.group_title == group_title,
|
||||
ChannelDB.name == name,
|
||||
ChannelDB.id != channel_id
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing_channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Channel with same group_title and name already exists"
|
||||
)
|
||||
|
||||
if existing_channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Channel with same group_title and name already exists"
|
||||
)
|
||||
|
||||
for key, value in channel.model_dump().items():
|
||||
# Update only provided fields
|
||||
update_data = channel.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(db_channel, key, value)
|
||||
|
||||
db.commit()
|
||||
@@ -160,12 +172,51 @@ def add_channel_url(
|
||||
detail="Channel not found"
|
||||
)
|
||||
|
||||
db_url = ChannelURL(channel_id=channel_id, url=url.url)
|
||||
db_url = ChannelURL(
|
||||
channel_id=channel_id,
|
||||
url=url.url,
|
||||
priority_id=url.priority_id,
|
||||
in_use=False # Default to not in use
|
||||
)
|
||||
db.add(db_url)
|
||||
db.commit()
|
||||
db.refresh(db_url)
|
||||
return db_url
|
||||
|
||||
@router.put("/{channel_id}/urls/{url_id}", response_model=ChannelURLResponse)
|
||||
@require_roles("admin")
|
||||
def update_channel_url(
|
||||
channel_id: UUID,
|
||||
url_id: UUID,
|
||||
url_update: ChannelURLUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
user: CognitoUser = Depends(get_current_user)
|
||||
):
|
||||
"""Update a channel URL (url, in_use, or priority_id)"""
|
||||
db_url = db.query(ChannelURL).filter(
|
||||
and_(
|
||||
ChannelURL.id == url_id,
|
||||
ChannelURL.channel_id == channel_id
|
||||
)
|
||||
).first()
|
||||
|
||||
if not db_url:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="URL not found"
|
||||
)
|
||||
|
||||
if url_update.url is not None:
|
||||
db_url.url = url_update.url
|
||||
if url_update.in_use is not None:
|
||||
db_url.in_use = url_update.in_use
|
||||
if url_update.priority_id is not None:
|
||||
db_url.priority_id = url_update.priority_id
|
||||
|
||||
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(
|
||||
|
||||
97
app/routers/priorities.py
Normal file
97
app/routers/priorities.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select, delete
|
||||
from typing import List
|
||||
|
||||
from app.models.db import Priority
|
||||
from app.models.schemas import PriorityCreate, PriorityResponse
|
||||
from app.utils.database import get_db
|
||||
from app.auth.dependencies import get_current_user, require_roles
|
||||
from app.models.auth import CognitoUser
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/priorities",
|
||||
tags=["priorities"]
|
||||
)
|
||||
|
||||
@router.post("/", response_model=PriorityResponse, status_code=status.HTTP_201_CREATED)
|
||||
@require_roles("admin")
|
||||
def create_priority(
|
||||
priority: PriorityCreate,
|
||||
db: Session = Depends(get_db),
|
||||
user: CognitoUser = Depends(get_current_user)
|
||||
):
|
||||
"""Create a new priority"""
|
||||
# Check if priority with this ID already exists
|
||||
existing = db.get(Priority, priority.id)
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Priority with ID {priority.id} already exists"
|
||||
)
|
||||
|
||||
db_priority = Priority(**priority.model_dump())
|
||||
db.add(db_priority)
|
||||
db.commit()
|
||||
db.refresh(db_priority)
|
||||
return db_priority
|
||||
|
||||
@router.get("/", response_model=List[PriorityResponse])
|
||||
@require_roles("admin")
|
||||
def list_priorities(
|
||||
db: Session = Depends(get_db),
|
||||
user: CognitoUser = Depends(get_current_user)
|
||||
):
|
||||
"""List all priorities"""
|
||||
return db.query(Priority).all()
|
||||
|
||||
@router.get("/{priority_id}", response_model=PriorityResponse)
|
||||
@require_roles("admin")
|
||||
def get_priority(
|
||||
priority_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
user: CognitoUser = Depends(get_current_user)
|
||||
):
|
||||
"""Get a priority by id"""
|
||||
priority = db.get(Priority, priority_id)
|
||||
if not priority:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Priority not found"
|
||||
)
|
||||
return priority
|
||||
|
||||
@router.delete("/{priority_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@require_roles("admin")
|
||||
def delete_priority(
|
||||
priority_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
user: CognitoUser = Depends(get_current_user)
|
||||
):
|
||||
"""Delete a priority (if not in use)"""
|
||||
from app.models.db import ChannelURL
|
||||
|
||||
# Check if priority exists
|
||||
priority = db.get(Priority, priority_id)
|
||||
if not priority:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Priority not found"
|
||||
)
|
||||
|
||||
# Check if priority is in use
|
||||
in_use = db.scalar(
|
||||
select(ChannelURL)
|
||||
.where(ChannelURL.priority_id == priority_id)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if in_use:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Cannot delete priority that is in use by channel URLs"
|
||||
)
|
||||
|
||||
db.execute(delete(Priority).where(Priority.id == priority_id))
|
||||
db.commit()
|
||||
return None
|
||||
Reference in New Issue
Block a user