Refactored DB schema. Channels table to have uuid as primary key. Modified endpoints accordingly
Some checks failed
AWS Deploy on Push / build (push) Failing after 1m9s

This commit is contained in:
2025-05-22 11:34:04 -05:00
parent 5ee6cb4be4
commit 9e8df169fc
5 changed files with 72 additions and 18 deletions

View File

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

View File

@@ -1,5 +1,7 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlalchemy import Column, String, JSON, DateTime import uuid
from sqlalchemy import Column, String, JSON, DateTime, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base() Base = declarative_base()
@@ -8,10 +10,15 @@ class ChannelDB(Base):
"""SQLAlchemy model for IPTV channels""" """SQLAlchemy model for IPTV channels"""
__tablename__ = "channels" __tablename__ = "channels"
tvg_id = Column(String, primary_key=True) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tvg_id = Column(String, nullable=False)
name = Column(String, nullable=False) name = Column(String, nullable=False)
group_title = Column(String) group_title = Column(String, nullable=False)
tvg_name = Column(String) tvg_name = Column(String)
__table_args__ = (
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 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))

View File

@@ -1,5 +1,6 @@
from datetime import datetime from datetime import datetime
from typing import List from typing import List
from uuid import UUID
from pydantic import BaseModel from pydantic import BaseModel
class ChannelCreate(BaseModel): class ChannelCreate(BaseModel):
@@ -11,7 +12,15 @@ class ChannelCreate(BaseModel):
tvg_logo: str tvg_logo: str
tvg_name: str tvg_name: str
class ChannelUpdate(ChannelCreate):
"""Pydantic model for updating channels"""
pass
class ChannelResponse(ChannelCreate): class ChannelResponse(ChannelCreate):
"""Pydantic model for channel responses""" """Pydantic model for channel responses"""
id: UUID
created_at: datetime created_at: datetime
updated_at: datetime updated_at: datetime
class Config:
from_attributes = True

View File

@@ -1,8 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from uuid import UUID
from sqlalchemy import and_
from app.models import ChannelDB, ChannelCreate, ChannelResponse from app.models import ChannelDB, ChannelCreate, ChannelUpdate, ChannelResponse
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
@@ -20,19 +22,33 @@ def create_channel(
user: CognitoUser = Depends(get_current_user) user: CognitoUser = Depends(get_current_user)
): ):
"""Create a new channel""" """Create a new channel"""
# Check for duplicate channel (same group_title + name)
existing_channel = db.query(ChannelDB).filter(
and_(
ChannelDB.group_title == channel.group_title,
ChannelDB.name == channel.name
)
).first()
if existing_channel:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Channel with same group_title and name already exists"
)
db_channel = ChannelDB(**channel.model_dump()) db_channel = ChannelDB(**channel.model_dump())
db.add(db_channel) db.add(db_channel)
db.commit() db.commit()
db.refresh(db_channel) db.refresh(db_channel)
return db_channel return db_channel
@router.get("/{tvg_id}", response_model=ChannelResponse) @router.get("/{channel_id}", response_model=ChannelResponse)
def get_channel( def get_channel(
tvg_id: str, channel_id: UUID,
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""Get a channel by tvg_id""" """Get a channel by id"""
channel = db.query(ChannelDB).filter(ChannelDB.tvg_id == tvg_id).first() channel = db.query(ChannelDB).filter(ChannelDB.id == channel_id).first()
if not channel: if not channel:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@@ -40,22 +56,37 @@ def get_channel(
) )
return channel return channel
@router.put("/{tvg_id}", response_model=ChannelResponse) @router.put("/{channel_id}", response_model=ChannelResponse)
@require_roles("admin") @require_roles("admin")
def update_channel( def update_channel(
tvg_id: str, channel_id: UUID,
channel: ChannelCreate, channel: ChannelUpdate,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: CognitoUser = Depends(get_current_user) user: CognitoUser = Depends(get_current_user)
): ):
"""Update a channel""" """Update a channel"""
db_channel = db.query(ChannelDB).filter(ChannelDB.tvg_id == tvg_id).first() db_channel = db.query(ChannelDB).filter(ChannelDB.id == channel_id).first()
if not db_channel: if not db_channel:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail="Channel not found" 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()
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(): for key, value in channel.model_dump().items():
setattr(db_channel, key, value) setattr(db_channel, key, value)
@@ -63,15 +94,15 @@ def update_channel(
db.refresh(db_channel) db.refresh(db_channel)
return db_channel return db_channel
@router.delete("/{tvg_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/{channel_id}", status_code=status.HTTP_204_NO_CONTENT)
@require_roles("admin") @require_roles("admin")
def delete_channel( def delete_channel(
tvg_id: str, channel_id: UUID,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: CognitoUser = Depends(get_current_user) user: CognitoUser = Depends(get_current_user)
): ):
"""Delete a channel""" """Delete a channel"""
channel = db.query(ChannelDB).filter(ChannelDB.tvg_id == tvg_id).first() channel = db.query(ChannelDB).filter(ChannelDB.id == channel_id).first()
if not channel: if not channel:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,

View File

@@ -210,6 +210,13 @@ class IptvUpdaterStack(Stack):
"Allow PostgreSQL access from EC2 instance" "Allow PostgreSQL access from EC2 instance"
) )
# Allow PostgreSQL access from developer's IP
rds_sg.add_ingress_rule(
ec2.Peer.ipv4("47.189.88.48/32"), # Replace with your actual IP
ec2.Port.tcp(5432),
"Allow PostgreSQL access from developer's IP"
)
# Create RDS PostgreSQL instance (free tier compatible - db.t3.micro) # Create RDS PostgreSQL instance (free tier compatible - db.t3.micro)
db = rds.DatabaseInstance( db = rds.DatabaseInstance(
self, "IptvUpdaterDB", self, "IptvUpdaterDB",