Changed project name to be IPTV Manager Service
All checks were successful
AWS Deploy on Push / build (push) Successful in 8m29s

This commit is contained in:
2025-05-29 16:09:52 -05:00
parent e25f8c1ecd
commit eaab1ef998
22 changed files with 202 additions and 138 deletions

View File

@@ -4,7 +4,7 @@ MOCK_AUTH=true/false
DB_USER=MyDBUser DB_USER=MyDBUser
DB_PASSWORD=MyDBPassword DB_PASSWORD=MyDBPassword
DB_HOST=MyDBHost DB_HOST=MyDBHost
DB_NAME=iptv_updater DB_NAME=iptv_manager
FREEDNS_User=MyFreeDNSUsername FREEDNS_User=MyFreeDNSUsername
FREEDNS_Password=MyFreeDNSPassword FREEDNS_Password=MyFreeDNSPassword

View File

@@ -58,7 +58,7 @@ jobs:
run: | run: |
INSTANCE_IDS=$(aws ec2 describe-instances \ INSTANCE_IDS=$(aws ec2 describe-instances \
--region us-east-2 \ --region us-east-2 \
--filters "Name=tag:Name,Values=IptvUpdaterStack/IptvUpdaterInstance" \ --filters "Name=tag:Name,Values=IptvManagerStack/IptvManagerInstance" \
"Name=instance-state-name,Values=running" \ "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].InstanceId" \ --query "Reservations[].Instances[].InstanceId" \
--output text) --output text)
@@ -69,11 +69,11 @@ jobs:
--instance-ids "$INSTANCE_ID" \ --instance-ids "$INSTANCE_ID" \
--document-name "AWS-RunShellScript" \ --document-name "AWS-RunShellScript" \
--parameters 'commands=[ --parameters 'commands=[
"cd /home/ec2-user/iptv-updater-aws", "cd /home/ec2-user/iptv-manager-service",
"git pull", "git pull",
"pip3 install -r requirements.txt", "pip3 install -r requirements.txt",
"alembic upgrade head", "alembic upgrade head",
"sudo systemctl restart iptv-updater" "sudo systemctl restart iptv-manager"
]' ]'
done done

View File

@@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.2 rev: v0.11.12
hooks: hooks:
- id: ruff - id: ruff
args: [--fix, --exit-non-zero-on-fix] args: [--fix, --exit-non-zero-on-fix]

View File

@@ -1,4 +1,6 @@
{ {
"python.terminal.activateEnvironment": true,
"python.terminal.activateEnvInCurrentTerminal": true,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff", "editor.defaultFormatter": "charliermarsh.ruff",
"ruff.importStrategy": "fromEnvironment", "ruff.importStrategy": "fromEnvironment",
@@ -31,10 +33,13 @@
"cluflogo", "cluflogo",
"clulogo", "clulogo",
"cpulogo", "cpulogo",
"crond",
"cronie",
"cuflgo", "cuflgo",
"CUNF", "CUNF",
"cunflogo", "cunflogo",
"cuulogo", "cuulogo",
"datname",
"deadstreams", "deadstreams",
"delenv", "delenv",
"delogo", "delogo",
@@ -50,6 +55,7 @@
"freedns", "freedns",
"fullchain", "fullchain",
"gitea", "gitea",
"httpx",
"iptv", "iptv",
"isort", "isort",
"KHTML", "KHTML",
@@ -61,7 +67,9 @@
"ondelete", "ondelete",
"onupdate", "onupdate",
"passlib", "passlib",
"PGPASSWORD",
"poolclass", "poolclass",
"psql",
"psycopg", "psycopg",
"pycache", "pycache",
"pycodestyle", "pycodestyle",
@@ -82,6 +90,7 @@
"testdb", "testdb",
"testpass", "testpass",
"testpaths", "testpaths",
"testuser",
"uflogo", "uflogo",
"umlogo", "umlogo",
"usefixtures", "usefixtures",

View File

@@ -1,6 +1,6 @@
# IPTV Updater AWS # IPTV Manager Service
An automated IPTV playlist and EPG updater service deployed on AWS infrastructure using CDK. An automated IPTV playlist and EPG manager service deployed on AWS infrastructure using CDK.
## Overview ## Overview
@@ -25,7 +25,7 @@ This project provides a service for automatically updating IPTV playlists and El
```bash ```bash
git clone <repo-url> git clone <repo-url>
cd iptv-updater-aws cd iptv-manager-service
``` ```
2. Copy the example environment file: 2. Copy the example environment file:
@@ -144,13 +144,13 @@ scripts/ # Utility scripts for deployment and management
The following environment variables are required: The following environment variables are required:
| Variable | Description | | Variable | Description |
|----------|-------------| | ----------------- | ------------------------------------ |
| FREEDNS_User | FreeDNS username | | FREEDNS_User | FreeDNS username |
| FREEDNS_Password | FreeDNS password | | FREEDNS_Password | FreeDNS password |
| DOMAIN_NAME | Your domain name | | DOMAIN_NAME | Your domain name |
| SSH_PUBLIC_KEY | SSH public key for EC2 access | | SSH_PUBLIC_KEY | SSH public key for EC2 access |
| REPO_URL | Repository URL | | REPO_URL | Repository URL |
| LETSENCRYPT_EMAIL | Email for Let's Encrypt certificates | | LETSENCRYPT_EMAIL | Email for Let's Encrypt certificates |
## Security Notes ## Security Notes

View File

@@ -15,7 +15,8 @@ config = context.config
if config.config_file_name is not None: if config.config_file_name is not None:
fileConfig(config.config_file_name) fileConfig(config.config_file_name)
# Setup target metadata for autogenerate support # add your model's MetaData object here
# for 'autogenerate' support
target_metadata = Base.metadata target_metadata = Base.metadata
# Override sqlalchemy.url with dynamic credentials # Override sqlalchemy.url with dynamic credentials

View File

@@ -1,59 +0,0 @@
"""Add priority and in_use fields
Revision ID: 036879e47172
Revises:
Create Date: 2025-05-26 19:21:32.285656
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '036879e47172'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
# 1. Create priorities table if not exists
if not op.get_bind().engine.dialect.has_table(op.get_bind(), 'priorities'):
op.create_table('priorities',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('description', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# 2. Insert default priorities (skip if already exists)
op.execute("""
INSERT INTO priorities (id, description)
VALUES (100, 'High'), (200, 'Medium'), (300, 'Low')
ON CONFLICT (id) DO NOTHING
""")
# Add new columns with temporary nullable=True
op.add_column('channels_urls', sa.Column('in_use', sa.Boolean(), nullable=True))
op.add_column('channels_urls', sa.Column('priority_id', sa.Integer(), nullable=True))
# Set default values
op.execute("UPDATE channels_urls SET in_use = false, priority_id = 100")
# Convert to NOT NULL
op.alter_column('channels_urls', 'in_use', nullable=False)
op.alter_column('channels_urls', 'priority_id', nullable=False)
op.create_foreign_key(None, 'channels_urls', 'priorities', ['priority_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('channels_urls_priority_id_fkey', 'channels_urls', type_='foreignkey')
op.drop_column('channels_urls', 'priority_id')
op.drop_column('channels_urls', 'in_use')
op.drop_table('priorities')
# ### end Alembic commands ###

View File

@@ -0,0 +1,79 @@
"""create initial tables
Revision ID: 95b61a92455a
Revises:
Create Date: 2025-05-29 14:42:16.239587
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '95b61a92455a'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('channels',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tvg_id', sa.String(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('group_title', sa.String(), nullable=False),
sa.Column('tvg_name', sa.String(), nullable=True),
sa.Column('tvg_logo', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('group_title', 'name', name='uix_group_title_name')
)
op.create_table('priorities',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('description', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('channels_urls',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('channel_id', sa.UUID(), nullable=False),
sa.Column('url', sa.String(), nullable=False),
sa.Column('in_use', sa.Boolean(), nullable=False),
sa.Column('priority_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['channel_id'], ['channels.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['priority_id'], ['priorities.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
# Seed initial priorities
op.bulk_insert(
sa.Table(
'priorities',
sa.MetaData(),
sa.Column('id', sa.Integer),
sa.Column('description', sa.String),
),
[
{'id': 100, 'description': 'High'},
{'id': 200, 'description': 'Medium'},
{'id': 300, 'description': 'Low'},
]
)
def downgrade() -> None:
"""Downgrade schema."""
# Remove seeded priorities
op.execute("DELETE FROM priorities WHERE id IN (100, 200, 300);")
# Drop tables
op.drop_table('channels_urls')
op.drop_table('priorities')
op.drop_table('channels')

6
app.py
View File

@@ -3,7 +3,7 @@ import os
import aws_cdk as cdk import aws_cdk as cdk
from infrastructure.stack import IptvUpdaterStack from infrastructure.stack import IptvManagerStack
app = cdk.App() app = cdk.App()
@@ -31,9 +31,9 @@ if missing_vars:
f"Missing required environment variables: {', '.join(missing_vars)}" f"Missing required environment variables: {', '.join(missing_vars)}"
) )
IptvUpdaterStack( IptvManagerStack(
app, app,
"IptvUpdaterStack", "IptvManagerStack",
freedns_user=freedns_user, freedns_user=freedns_user,
freedns_password=freedns_password, freedns_password=freedns_password,
domain_name=domain_name, domain_name=domain_name,

View File

@@ -15,8 +15,8 @@ async def lifespan(app: FastAPI):
app = FastAPI( app = FastAPI(
lifespan=lifespan, lifespan=lifespan,
title="IPTV Updater API", title="IPTV Manager API",
description="API for IPTV Updater service", description="API for IPTV Manager service",
version="1.0.0", version="1.0.0",
) )
@@ -60,7 +60,7 @@ app.openapi = custom_openapi
@app.get("/") @app.get("/")
async def root(): async def root():
return {"message": "IPTV Updater API"} return {"message": "IPTV Manager API"}
# Include routers # Include routers

View File

@@ -19,16 +19,16 @@ def get_db_credentials():
ssm = boto3.client("ssm", region_name=AWS_REGION) ssm = boto3.client("ssm", region_name=AWS_REGION)
try: try:
host = ssm.get_parameter(Name="/iptv-updater/DB_HOST", WithDecryption=True)[ host = ssm.get_parameter(Name="/iptv-manager/DB_HOST", WithDecryption=True)[
"Parameter" "Parameter"
]["Value"] ]["Value"]
user = ssm.get_parameter(Name="/iptv-updater/DB_USER", WithDecryption=True)[ user = ssm.get_parameter(Name="/iptv-manager/DB_USER", WithDecryption=True)[
"Parameter" "Parameter"
]["Value"] ]["Value"]
password = ssm.get_parameter( password = ssm.get_parameter(
Name="/iptv-updater/DB_PASSWORD", WithDecryption=True Name="/iptv-manager/DB_PASSWORD", WithDecryption=True
)["Parameter"]["Value"] )["Parameter"]["Value"]
dbname = ssm.get_parameter(Name="/iptv-updater/DB_NAME", WithDecryption=True)[ dbname = ssm.get_parameter(Name="/iptv-manager/DB_NAME", WithDecryption=True)[
"Parameter" "Parameter"
]["Value"] ]["Value"]
return f"postgresql://{user}:{password}@{host}/{dbname}" return f"postgresql://{user}:{password}@{host}/{dbname}"

View File

@@ -3,10 +3,11 @@ version: '3.8'
services: services:
postgres: postgres:
image: postgres:13 image: postgres:13
container_name: postgres
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_DB: iptv_updater POSTGRES_DB: iptv_manager
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:

View File

@@ -6,7 +6,7 @@ services:
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_DB: iptv_updater POSTGRES_DB: iptv_manager
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:
@@ -20,7 +20,7 @@ services:
DB_USER: postgres DB_USER: postgres
DB_PASSWORD: postgres DB_PASSWORD: postgres
DB_HOST: postgres DB_HOST: postgres
DB_NAME: iptv_updater DB_NAME: iptv_manager
MOCK_AUTH: "true" MOCK_AUTH: "true"
ports: ports:
- "8000:8000" - "8000:8000"

View File

@@ -9,7 +9,7 @@ from aws_cdk import aws_ssm as ssm
from constructs import Construct from constructs import Construct
class IptvUpdaterStack(Stack): class IptvManagerStack(Stack):
def __init__( def __init__(
self, self,
scope: Construct, scope: Construct,
@@ -27,7 +27,7 @@ class IptvUpdaterStack(Stack):
# Create VPC # Create VPC
vpc = ec2.Vpc( vpc = ec2.Vpc(
self, self,
"IptvUpdaterVPC", "IptvManagerVPC",
max_azs=2, # Need at least 2 AZs for RDS subnet group max_azs=2, # Need at least 2 AZs for RDS subnet group
nat_gateways=0, # No NAT Gateway to stay in free tier nat_gateways=0, # No NAT Gateway to stay in free tier
subnet_configuration=[ subnet_configuration=[
@@ -44,7 +44,7 @@ class IptvUpdaterStack(Stack):
# Security Group # Security Group
security_group = ec2.SecurityGroup( security_group = ec2.SecurityGroup(
self, "IptvUpdaterSG", vpc=vpc, allow_all_outbound=True self, "IptvManagerSG", vpc=vpc, allow_all_outbound=True
) )
security_group.add_ingress_rule( security_group.add_ingress_rule(
@@ -66,18 +66,18 @@ class IptvUpdaterStack(Stack):
"Allow PostgreSQL traffic for tunneling", "Allow PostgreSQL traffic for tunneling",
) )
# Key pair for IPTV Updater instance # Key pair for IPTV Manager instance
key_pair = ec2.KeyPair( key_pair = ec2.KeyPair(
self, self,
"IptvUpdaterKeyPair", "IptvManagerKeyPair",
key_pair_name="iptv-updater-key", key_pair_name="iptv-manager-key",
public_key_material=ssh_public_key, public_key_material=ssh_public_key,
) )
# Create IAM role for EC2 # Create IAM role for EC2
role = iam.Role( role = iam.Role(
self, self,
"IptvUpdaterRole", "IptvManagerRole",
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"), assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
) )
@@ -114,7 +114,7 @@ class IptvUpdaterStack(Stack):
# EC2 Instance # EC2 Instance
instance = ec2.Instance( instance = ec2.Instance(
self, self,
"IptvUpdaterInstance", "IptvManagerInstance",
vpc=vpc, vpc=vpc,
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC), vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC),
instance_type=ec2.InstanceType.of( instance_type=ec2.InstanceType.of(
@@ -132,7 +132,7 @@ class IptvUpdaterStack(Stack):
# Option: 2: Create Elastic IP (not free tier compatible) # Option: 2: Create Elastic IP (not free tier compatible)
# eip = ec2.CfnEIP( # eip = ec2.CfnEIP(
# self, "IptvUpdaterEIP", # self, "IptvManagerEIP",
# domain="vpc", # domain="vpc",
# instance_id=instance.instance_id # instance_id=instance.instance_id
# ) # )
@@ -140,8 +140,8 @@ class IptvUpdaterStack(Stack):
# Add Cognito User Pool # Add Cognito User Pool
user_pool = cognito.UserPool( user_pool = cognito.UserPool(
self, self,
"IptvUpdaterUserPool", "IptvManagerUserPool",
user_pool_name="iptv-updater-users", user_pool_name="iptv-manager-users",
self_sign_up_enabled=False, # Only admins can create users self_sign_up_enabled=False, # Only admins can create users
password_policy=cognito.PasswordPolicy( password_policy=cognito.PasswordPolicy(
min_length=8, min_length=8,
@@ -156,7 +156,7 @@ class IptvUpdaterStack(Stack):
# Add App Client with the correct callback URL # Add App Client with the correct callback URL
client = user_pool.add_client( client = user_pool.add_client(
"IptvUpdaterClient", "IptvManagerClient",
access_token_validity=Duration.minutes(60), access_token_validity=Duration.minutes(60),
id_token_validity=Duration.minutes(60), id_token_validity=Duration.minutes(60),
refresh_token_validity=Duration.days(1), refresh_token_validity=Duration.days(1),
@@ -171,8 +171,8 @@ class IptvUpdaterStack(Stack):
# Add domain for hosted UI # Add domain for hosted UI
domain = user_pool.add_domain( domain = user_pool.add_domain(
"IptvUpdaterDomain", "IptvManagerDomain",
cognito_domain=cognito.CognitoDomainOptions(domain_prefix="iptv-updater"), cognito_domain=cognito.CognitoDomainOptions(domain_prefix="iptv-manager"),
) )
# Read the userdata script with proper path resolution # Read the userdata script with proper path resolution
@@ -226,7 +226,7 @@ class IptvUpdaterStack(Stack):
# 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, self,
"IptvUpdaterDB", "IptvManagerDB",
engine=rds.DatabaseInstanceEngine.postgres( engine=rds.DatabaseInstanceEngine.postgres(
version=rds.PostgresEngineVersion.VER_13 version=rds.PostgresEngineVersion.VER_13
), ),
@@ -240,7 +240,7 @@ class IptvUpdaterStack(Stack):
security_groups=[rds_sg], security_groups=[rds_sg],
allocated_storage=10, allocated_storage=10,
max_allocated_storage=10, max_allocated_storage=10,
database_name="iptv_updater", database_name="iptv_manager",
removal_policy=RemovalPolicy.DESTROY, removal_policy=RemovalPolicy.DESTROY,
deletion_protection=False, deletion_protection=False,
publicly_accessible=False, # Avoid public IPv4 charges publicly_accessible=False, # Avoid public IPv4 charges
@@ -255,25 +255,25 @@ class IptvUpdaterStack(Stack):
ssm.StringParameter( ssm.StringParameter(
self, self,
"DBHostParam", "DBHostParam",
parameter_name="/iptv-updater/DB_HOST", parameter_name="/iptv-manager/DB_HOST",
string_value=db.db_instance_endpoint_address, string_value=db.db_instance_endpoint_address,
) )
ssm.StringParameter( ssm.StringParameter(
self, self,
"DBNameParam", "DBNameParam",
parameter_name="/iptv-updater/DB_NAME", parameter_name="/iptv-manager/DB_NAME",
string_value="iptv_updater", string_value="iptv_manager",
) )
ssm.StringParameter( ssm.StringParameter(
self, self,
"DBUserParam", "DBUserParam",
parameter_name="/iptv-updater/DB_USER", parameter_name="/iptv-manager/DB_USER",
string_value=db.secret.secret_value_from_json("username").to_string(), string_value=db.secret.secret_value_from_json("username").to_string(),
) )
ssm.StringParameter( ssm.StringParameter(
self, self,
"DBPassParam", "DBPassParam",
parameter_name="/iptv-updater/DB_PASSWORD", parameter_name="/iptv-manager/DB_PASSWORD",
string_value=db.secret.secret_value_from_json("password").to_string(), string_value=db.secret.secret_value_from_json("password").to_string(),
) )

View File

@@ -2,7 +2,7 @@
# Update system and install required packages # Update system and install required packages
dnf update -y dnf update -y
dnf install -y python3-pip git cronie nginx certbot python3-certbot-nginx dnf install -y python3-pip git cronie nginx certbot python3-certbot-nginx postgresql awscli
# Start and enable crond service # Start and enable crond service
systemctl start crond systemctl start crond
@@ -11,27 +11,48 @@ systemctl enable crond
cd /home/ec2-user cd /home/ec2-user
git clone ${REPO_URL} git clone ${REPO_URL}
cd iptv-updater-aws cd iptv-manager-service
# Install Python packages with --ignore-installed to prevent conflicts with RPM packages # Install Python packages with --ignore-installed to prevent conflicts with RPM packages
pip3 install --ignore-installed -r requirements.txt pip3 install --ignore-installed -r requirements.txt
# Retrieve DB credentials from SSM Parameter Store
export DB_HOST=$(aws ssm get-parameter --name "/iptv-manager/DB_HOST" --query "Parameter.Value" --output text)
export DB_NAME=$(aws ssm get-parameter --name "/iptv-manager/DB_NAME" --query "Parameter.Value" --output text)
export DB_USER=$(aws ssm get-parameter --name "/iptv-manager/DB_USER" --query "Parameter.Value" --output text)
export DB_PASSWORD=$(aws ssm get-parameter --name "/iptv-manager/DB_PASSWORD" --query "Parameter.Value" --output text)
# Set PGPASSWORD for psql to use
export PGPASSWORD=$DB_PASSWORD
# Wait for PostgreSQL to be ready
echo "Waiting for PostgreSQL to start..."
until psql -h $DB_HOST -U $DB_USER -d postgres -c '\q'; do
sleep 1
done
echo "PostgreSQL is ready."
# Create database if it does not exist
DB_EXISTS=$(psql -h $DB_HOST -U $DB_USER -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = '$DB_NAME';")
if [ -z "$DB_EXISTS" ]; then
echo "Creating database $DB_NAME..."
psql -h $DB_HOST -U $DB_USER -d postgres -c "CREATE DATABASE $DB_NAME;"
echo "Database $DB_NAME created."
fi
# Run database migrations # Run database migrations
alembic upgrade head alembic upgrade head
# Seed initial priorities
python3 -c "from app.utils.database import SessionLocal; from app.models.db import Priority; db = SessionLocal(); db.add_all([Priority(id=100, description='High'), Priority(id=200, description='Medium'), Priority(id=300, description='Low')]); db.commit()"
# Create systemd service file # Create systemd service file
cat << 'EOF' > /etc/systemd/system/iptv-updater.service cat << 'EOF' > /etc/systemd/system/iptv-manager.service
[Unit] [Unit]
Description=IPTV Updater Service Description=IPTV Manager Service
After=network.target After=network.target
[Service] [Service]
Type=simple Type=simple
User=ec2-user User=ec2-user
WorkingDirectory=/home/ec2-user/iptv-updater-aws WorkingDirectory=/home/ec2-user/iptv-manager-service
ExecStart=/usr/local/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 ExecStart=/usr/local/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
EnvironmentFile=/etc/environment EnvironmentFile=/etc/environment
Restart=always Restart=always
@@ -56,7 +77,7 @@ sudo mkdir -p /etc/nginx/ssl
--reloadcmd "service nginx force-reload" --reloadcmd "service nginx force-reload"
# Create nginx config # Create nginx config
cat << EOF > /etc/nginx/conf.d/iptvUpdater.conf cat << EOF > /etc/nginx/conf.d/iptvManager.conf
server { server {
listen 80; listen 80;
server_name ${DOMAIN_NAME} *.${DOMAIN_NAME}; server_name ${DOMAIN_NAME} *.${DOMAIN_NAME};
@@ -83,5 +104,5 @@ EOF
# Start nginx service # Start nginx service
systemctl enable nginx systemctl enable nginx
systemctl start nginx systemctl start nginx
systemctl enable iptv-updater systemctl enable iptv-manager
systemctl start iptv-updater systemctl start iptv-manager

View File

@@ -5,12 +5,21 @@ python_functions = test_*
asyncio_mode = auto asyncio_mode = auto
filterwarnings = filterwarnings =
ignore::DeprecationWarning:botocore.auth ignore::DeprecationWarning:botocore.auth
ignore:The 'app' shortcut is now deprecated:DeprecationWarning:httpx._client
# Coverage configuration # Coverage configuration
addopts = addopts =
--cov=app --cov=app
--cov-report=term-missing --cov-report=term-missing
# Test environment variables
env =
MOCK_AUTH=true
DB_USER=test_user
DB_PASSWORD=test_password
DB_HOST=localhost
DB_NAME=iptv_manager_test
# Test markers # Test markers
markers = markers =
slow: mark tests as slow running slow: mark tests as slow running

View File

@@ -14,4 +14,8 @@ psycopg2-binary==2.9.9
alembic==1.16.1 alembic==1.16.1
pytest==8.1.1 pytest==8.1.1
pytest-asyncio==0.23.6 pytest-asyncio==0.23.6
pytest-mock==3.12.0 pytest-mock==3.12.0
pytest-cov==4.1.0
pytest-env==1.1.1
httpx==0.27.0
pre-commit

View File

@@ -25,7 +25,7 @@ cdk deploy --app="python3 ${PWD}/app.py"
# Update application on running instances # Update application on running instances
INSTANCE_IDS=$(aws ec2 describe-instances \ INSTANCE_IDS=$(aws ec2 describe-instances \
--region us-east-2 \ --region us-east-2 \
--filters "Name=tag:Name,Values=IptvUpdaterStack/IptvUpdaterInstance" \ --filters "Name=tag:Name,Values=IptvManagerStack/IptvManagerInstance" \
"Name=instance-state-name,Values=running" \ "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].InstanceId" \ --query "Reservations[].Instances[].InstanceId" \
--output text) --output text)
@@ -35,7 +35,7 @@ for INSTANCE_ID in $INSTANCE_IDS; do
aws ssm send-command \ aws ssm send-command \
--instance-ids "$INSTANCE_ID" \ --instance-ids "$INSTANCE_ID" \
--document-name "AWS-RunShellScript" \ --document-name "AWS-RunShellScript" \
--parameters '{"commands":["cd /home/ec2-user/iptv-updater-aws && git pull && pip3 install -r requirements.txt && alembic upgrade head && sudo systemctl restart iptv-updater"]}' \ --parameters '{"commands":["cd /home/ec2-user/iptv-manager-service && git pull && pip3 install -r requirements.txt && alembic upgrade head && sudo systemctl restart iptv-manager"]}' \
--no-cli-pager \ --no-cli-pager \
--no-paginate --no-paginate
done done

View File

@@ -10,10 +10,4 @@ pre-commit install-hooks
pre-commit autoupdate pre-commit autoupdate
# Verify pytest setup # Verify pytest setup
python3 -m pytest python3 -m pytest
# Initialize and run database migrations
alembic upgrade head
# Seed initial data
python3 -c "from app.utils.database import SessionLocal; from app.models.db import Priority; db = SessionLocal(); db.add_all([Priority(id=100, description='High'), Priority(id=200, description='Medium'), Priority(id=300, description='Low')]); db.commit()"

View File

@@ -1,21 +1,26 @@
#!/bin/bash #!/bin/bash
set -e
# Start PostgreSQL # Start PostgreSQL
docker-compose -f docker/docker-compose-db.yml up -d docker-compose -f docker/docker-compose-db.yml up -d
# Set mock auth and database environment variables # Set environment variables
export MOCK_AUTH=true export MOCK_AUTH=true
export DB_HOST=localhost
export DB_USER=postgres export DB_USER=postgres
export DB_PASSWORD=postgres export DB_PASSWORD=postgres
export DB_HOST=localhost export DB_NAME=iptv_manager
export DB_NAME=iptv_updater
echo "Ensuring database $DB_NAME exists using conditional DDL..."
PGPASSWORD=$DB_PASSWORD docker exec -i postgres psql -U $DB_USER <<< "SELECT 'CREATE DATABASE $DB_NAME' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$DB_NAME')\gexec"
echo "Database $DB_NAME check complete."
# Run database migrations # Run database migrations
alembic upgrade head alembic upgrade head
# Start FastAPI # Start FastAPI
nohup uvicorn app.main:app --host 127.0.0.1 --port 8000 > app.log 2>&1 & nohup uvicorn app.main:app --host 127.0.0.1 --port 8000 > app.log 2>&1 &
echo $! > iptv-updater.pid echo $! > iptv-manager.pid
echo "Services started:" echo "Services started:"
echo "- PostgreSQL running on localhost:5432" echo "- PostgreSQL running on localhost:5432"

View File

@@ -1,9 +1,9 @@
#!/bin/bash #!/bin/bash
# Stop FastAPI # Stop FastAPI
if [ -f iptv-updater.pid ]; then if [ -f iptv-manager.pid ]; then
kill $(cat iptv-updater.pid) kill $(cat iptv-manager.pid)
rm iptv-updater.pid rm iptv-manager.pid
echo "Stopped FastAPI" echo "Stopped FastAPI"
fi fi

View File

@@ -16,7 +16,7 @@ def test_root_endpoint(client):
"""Test root endpoint returns expected message""" """Test root endpoint returns expected message"""
response = client.get("/") response = client.get("/")
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"message": "IPTV Updater API"} assert response.json() == {"message": "IPTV Manager API"}
def test_openapi_schema_generation(client): def test_openapi_schema_generation(client):