1326 lines
52 KiB
Python
1326 lines
52 KiB
Python
import mysql.connector
|
|
import os
|
|
import sys
|
|
from cryptography.fernet import Fernet
|
|
import string
|
|
import secrets
|
|
import logging
|
|
import random
|
|
from argon2 import PasswordHasher
|
|
from argon2.exceptions import HashingError
|
|
from passlib.hash import argon2
|
|
|
|
# Set up basic configuration for logging
|
|
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
# Append the pinepods directory to sys.path for module import
|
|
sys.path.append('/pinepods')
|
|
|
|
try:
|
|
# Attempt to import additional modules
|
|
import database_functions.functions
|
|
# import Auth.Passfunctions
|
|
|
|
def hash_password(password: str):
|
|
# Hash the password
|
|
hashed_password = argon2.hash(password)
|
|
# Argon2 includes the salt in the hashed output
|
|
return hashed_password
|
|
|
|
# Retrieve database connection details from environment variables
|
|
db_host = os.environ.get("DB_HOST", "127.0.0.1")
|
|
db_port = os.environ.get("DB_PORT", "3306")
|
|
db_user = os.environ.get("DB_USER", "root")
|
|
db_password = os.environ.get("DB_PASSWORD", "password")
|
|
db_name = os.environ.get("DB_NAME", "pypods_database")
|
|
|
|
# Attempt to create a database connector
|
|
cnx = mysql.connector.connect(
|
|
host=db_host,
|
|
port=db_port,
|
|
user=db_user,
|
|
password=db_password,
|
|
database=db_name,
|
|
charset='utf8mb4',
|
|
collation="utf8mb4_general_ci"
|
|
)
|
|
|
|
# Create a cursor to execute SQL statements
|
|
cursor = cnx.cursor()
|
|
|
|
# Function to ensure all usernames are lowercase
|
|
def ensure_usernames_lowercase(cnx):
|
|
cursor = cnx.cursor()
|
|
cursor.execute('SELECT UserID, Username FROM Users')
|
|
users = cursor.fetchall()
|
|
for user_id, username in users:
|
|
if username != username.lower():
|
|
cursor.execute('UPDATE Users SET Username = %s WHERE UserID = %s', (username.lower(), user_id))
|
|
print(f"Updated Username for UserID {user_id} to lowercase")
|
|
cnx.commit()
|
|
cursor.close()
|
|
|
|
# Function to check and add columns if they don't exist
|
|
def add_column_if_not_exists(cursor, table_name, column_name, column_definition):
|
|
cursor.execute(f"""
|
|
SELECT COUNT(*)
|
|
FROM information_schema.columns
|
|
WHERE table_name='{table_name}'
|
|
AND column_name='{column_name}'
|
|
AND table_schema=DATABASE();
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute(f"""
|
|
ALTER TABLE {table_name}
|
|
ADD COLUMN {column_name} {column_definition};
|
|
""")
|
|
print(f"Column '{column_name}' added to table '{table_name}'")
|
|
else:
|
|
return
|
|
|
|
# Create Users table if it doesn't exist (your existing code)
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS Users (
|
|
UserID INT AUTO_INCREMENT PRIMARY KEY,
|
|
Fullname VARCHAR(255),
|
|
Username VARCHAR(255),
|
|
Email VARCHAR(255),
|
|
Hashed_PW CHAR(255),
|
|
IsAdmin TINYINT(1),
|
|
Reset_Code TEXT,
|
|
Reset_Expiry DATETIME,
|
|
MFA_Secret VARCHAR(70),
|
|
TimeZone VARCHAR(50) DEFAULT 'UTC',
|
|
TimeFormat INT DEFAULT 24,
|
|
DateFormat VARCHAR(3) DEFAULT 'ISO',
|
|
FirstLogin TINYINT(1) DEFAULT 0,
|
|
GpodderUrl VARCHAR(255) DEFAULT '',
|
|
Pod_Sync_Type VARCHAR(50) DEFAULT 'None',
|
|
GpodderLoginName VARCHAR(255) DEFAULT '',
|
|
GpodderToken VARCHAR(255) DEFAULT '',
|
|
EnableRSSFeeds TINYINT(1) DEFAULT 0,
|
|
auth_type VARCHAR(50) DEFAULT 'standard',
|
|
oidc_provider_id INT,
|
|
oidc_subject VARCHAR(255),
|
|
PlaybackSpeed DECIMAL(2,1) UNSIGNED DEFAULT 1.0,
|
|
UNIQUE (Username)
|
|
)
|
|
""")
|
|
|
|
# Create OIDCProviders table if it doesn't exist (MySQL version)
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS OIDCProviders (
|
|
ProviderID INT AUTO_INCREMENT PRIMARY KEY,
|
|
ProviderName VARCHAR(255) NOT NULL,
|
|
ClientID VARCHAR(255) NOT NULL,
|
|
ClientSecret VARCHAR(500) NOT NULL,
|
|
AuthorizationURL VARCHAR(255) NOT NULL,
|
|
TokenURL VARCHAR(255) NOT NULL,
|
|
UserInfoURL VARCHAR(255) NOT NULL,
|
|
Scope VARCHAR(255) DEFAULT 'openid email profile',
|
|
ButtonColor VARCHAR(50) DEFAULT '#000000',
|
|
ButtonText VARCHAR(255) NOT NULL,
|
|
ButtonTextColor VARCHAR(50) DEFAULT '#000000',
|
|
IconSVG TEXT,
|
|
Enabled TINYINT(1) DEFAULT 1,
|
|
Created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
Modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
|
|
# Function to add PlaybackSpeed to Users table for MySQL
|
|
def add_playbackspeed_if_not_exist_users_mysql(cursor, cnx):
|
|
try:
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'Users'
|
|
AND COLUMN_NAME = 'PlaybackSpeed'
|
|
""")
|
|
existing_column = cursor.fetchone()
|
|
if not existing_column:
|
|
cursor.execute("""
|
|
ALTER TABLE Users
|
|
ADD COLUMN PlaybackSpeed DECIMAL(2,1) UNSIGNED DEFAULT 1.0
|
|
""")
|
|
print("Added 'PlaybackSpeed' column to 'Users' table.")
|
|
cnx.commit()
|
|
else:
|
|
print("Column 'PlaybackSpeed' already exists in 'Users' table.")
|
|
except Exception as e:
|
|
print(f"Error checking PlaybackSpeed column in Users table: {e}")
|
|
|
|
add_playbackspeed_if_not_exist_users_mysql(cursor, cnx)
|
|
|
|
# Add new columns to Users table if they don't exist
|
|
add_column_if_not_exists(cursor, 'Users', 'auth_type', 'VARCHAR(50) DEFAULT \'standard\'')
|
|
add_column_if_not_exists(cursor, 'Users', 'oidc_provider_id', 'INT')
|
|
add_column_if_not_exists(cursor, 'Users', 'oidc_subject', 'VARCHAR(255)')
|
|
|
|
# Check if foreign key exists before adding it
|
|
cursor.execute("""
|
|
SELECT COUNT(*)
|
|
FROM information_schema.table_constraints
|
|
WHERE constraint_name = 'fk_oidc_provider'
|
|
AND table_name = 'Users'
|
|
AND table_schema = DATABASE();
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("""
|
|
ALTER TABLE Users
|
|
ADD CONSTRAINT fk_oidc_provider
|
|
FOREIGN KEY (oidc_provider_id)
|
|
REFERENCES OIDCProviders(ProviderID);
|
|
""")
|
|
print("Foreign key constraint 'fk_oidc_provider' added")
|
|
|
|
# Add EnableRSSFeeds column if it doesn't exist
|
|
cursor.execute("""
|
|
SELECT COUNT(*)
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'Users'
|
|
AND column_name = 'EnableRSSFeeds'
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("ALTER TABLE Users ADD COLUMN EnableRSSFeeds TINYINT(1) DEFAULT 0")
|
|
|
|
cursor.execute("""
|
|
SELECT COUNT(*)
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'Users'
|
|
AND column_name = 'PlaybackSpeed'
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("ALTER TABLE Users ADD COLUMN PlaybackSpeed DECIMAL(2,1) UNSIGNED DEFAULT 1.0")
|
|
|
|
# Add EnableRSSFeeds column if it doesn't exist
|
|
cursor.execute("""
|
|
SELECT COUNT(*)
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'Podcasts'
|
|
AND column_name = 'PlaybackSpeed'
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("ALTER TABLE Podcasts ADD COLUMN PlaybackSpeed DECIMAL(2,1) UNSIGNED DEFAULT 1.0")
|
|
|
|
|
|
ensure_usernames_lowercase(cnx)
|
|
|
|
def add_pod_sync_if_not_exists(cursor, table_name, column_name, column_definition):
|
|
cursor.execute(f"""
|
|
SELECT COUNT(*)
|
|
FROM information_schema.columns
|
|
WHERE table_name='{table_name}'
|
|
AND column_name='{column_name}';
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute(f"""
|
|
ALTER TABLE {table_name}
|
|
ADD COLUMN {column_name} {column_definition};
|
|
""")
|
|
print(f"Column '{column_name}' added to table '{table_name}'")
|
|
else:
|
|
return
|
|
|
|
add_pod_sync_if_not_exists(cursor, 'Users', 'Pod_Sync_Type', 'VARCHAR(50) DEFAULT \'None\'')
|
|
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS APIKeys (
|
|
APIKeyID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
APIKey TEXT,
|
|
Created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE
|
|
)""")
|
|
cnx.commit()
|
|
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS RssKeys (
|
|
RssKeyID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
RssKey TEXT,
|
|
Created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE
|
|
)""")
|
|
cnx.commit()
|
|
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS RssKeyMap (
|
|
RssKeyID INT,
|
|
PodcastID INT,
|
|
FOREIGN KEY (RssKeyID) REFERENCES RssKeys(RssKeyID) ON DELETE CASCADE
|
|
)""")
|
|
cnx.commit()
|
|
|
|
try:
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS GpodderDevices (
|
|
DeviceID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceName VARCHAR(255) NOT NULL,
|
|
DeviceType VARCHAR(50) DEFAULT 'desktop',
|
|
DeviceCaption VARCHAR(255),
|
|
IsDefault BOOLEAN DEFAULT FALSE,
|
|
LastSync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
IsActive BOOLEAN DEFAULT TRUE,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceName)
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
print("Created GpodderDevices table")
|
|
|
|
# Create index for faster lookups
|
|
# Check if index exists before creating it
|
|
cursor.execute("""
|
|
SELECT COUNT(1) IndexExists FROM INFORMATION_SCHEMA.STATISTICS
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'GpodderDevices'
|
|
AND index_name = 'idx_gpodder_devices_userid'
|
|
""")
|
|
index_exists = cursor.fetchone()[0]
|
|
|
|
if index_exists == 0:
|
|
# Create index only if it doesn't exist
|
|
cursor.execute("""
|
|
CREATE INDEX idx_gpodder_devices_userid
|
|
ON GpodderDevices(UserID)
|
|
""")
|
|
cnx.commit()
|
|
print("Created index idx_gpodder_devices_userid")
|
|
else:
|
|
print("Index idx_gpodder_devices_userid already exists")
|
|
|
|
# Create a table for subscription history/sync state
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncState (
|
|
SyncStateID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT NOT NULL,
|
|
LastTimestamp BIGINT DEFAULT 0,
|
|
EpisodesTimestamp BIGINT DEFAULT 0,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceID)
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
print("Created GpodderSyncState table")
|
|
except Exception as e:
|
|
print(f"Error creating GPodder tables: {e}")
|
|
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS UserStats (
|
|
UserStatsID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
UserCreated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
PodcastsPlayed INT DEFAULT 0,
|
|
TimeListened INT DEFAULT 0,
|
|
PodcastsAdded INT DEFAULT 0,
|
|
EpisodesSaved INT DEFAULT 0,
|
|
EpisodesDownloaded INT DEFAULT 0,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID)
|
|
)""")
|
|
|
|
# Generate a key
|
|
key = Fernet.generate_key()
|
|
|
|
# Create the AppSettings table
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS AppSettings (
|
|
AppSettingsID INT AUTO_INCREMENT PRIMARY KEY,
|
|
SelfServiceUser TINYINT(1) DEFAULT 0,
|
|
DownloadEnabled TINYINT(1) DEFAULT 1,
|
|
EncryptionKey BINARY(44), -- Set the data type to BINARY(32) to hold the 32-byte key
|
|
NewsFeedSubscribed TINYINT(1) DEFAULT 0
|
|
)
|
|
""")
|
|
|
|
cursor.execute("SELECT COUNT(*) FROM AppSettings WHERE AppSettingsID = 1")
|
|
count = cursor.fetchone()[0]
|
|
|
|
if count == 0:
|
|
cursor.execute("""
|
|
INSERT INTO AppSettings (SelfServiceUser, DownloadEnabled, EncryptionKey)
|
|
VALUES (0, 1, %s)
|
|
""", (key,))
|
|
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS EmailSettings (
|
|
EmailSettingsID INT AUTO_INCREMENT PRIMARY KEY,
|
|
Server_Name VARCHAR(255),
|
|
Server_Port INT,
|
|
From_Email VARCHAR(255),
|
|
Send_Mode VARCHAR(255),
|
|
Encryption VARCHAR(255),
|
|
Auth_Required TINYINT(1),
|
|
Username VARCHAR(255),
|
|
Password VARCHAR(255)
|
|
)
|
|
""")
|
|
|
|
cursor.execute("""
|
|
SELECT COUNT(*) FROM EmailSettings
|
|
""")
|
|
rows = cursor.fetchone()
|
|
|
|
if rows[0] == 0:
|
|
cursor.execute("""
|
|
INSERT INTO EmailSettings (Server_Name, Server_Port, From_Email, Send_Mode, Encryption, Auth_Required, Username, Password)
|
|
VALUES ('default_server', 587, 'default_email@domain.com', 'default_mode', 'default_encryption', 1, 'default_username', 'default_password')
|
|
""")
|
|
|
|
# Generate a random password
|
|
def generate_random_password(length=12):
|
|
characters = string.ascii_letters + string.digits + string.punctuation
|
|
return ''.join(random.choice(characters) for i in range(length))
|
|
|
|
# Hash the password using Argon2
|
|
def hash_password(password):
|
|
ph = PasswordHasher()
|
|
try:
|
|
return ph.hash(password)
|
|
except HashingError as e:
|
|
print(f"Error hashing password: {e}")
|
|
return None
|
|
|
|
# Check if a user with the username 'guest' exists
|
|
def user_exists(cursor, username):
|
|
cursor.execute("""
|
|
SELECT 1 FROM Users WHERE Username = %s
|
|
""", (username,))
|
|
return cursor.fetchone() is not None
|
|
|
|
def insert_or_update_user(cursor, hashed_password):
|
|
try:
|
|
# First, check if 'background_tasks' user exists
|
|
cursor.execute("SELECT * FROM Users WHERE Username = %s", ('background_tasks',))
|
|
existing_user = cursor.fetchone()
|
|
|
|
if existing_user:
|
|
# Update existing 'background_tasks' user
|
|
cursor.execute("""
|
|
UPDATE Users
|
|
SET Fullname = %s, Email = %s, Hashed_PW = %s, IsAdmin = %s
|
|
WHERE Username = %s
|
|
""", ('Background Tasks', 'inactive', hashed_password, False, 'background_tasks'))
|
|
logging.info("Updated existing 'background_tasks' user.")
|
|
else:
|
|
# Check for 'guest' or 'bt' users to update
|
|
cursor.execute("SELECT Username FROM Users WHERE Username IN ('guest', 'bt')")
|
|
old_user = cursor.fetchone()
|
|
|
|
if old_user:
|
|
# Update old user to 'background_tasks'
|
|
cursor.execute("""
|
|
UPDATE Users
|
|
SET Fullname = %s, Username = %s, Email = %s, Hashed_PW = %s, IsAdmin = %s
|
|
WHERE Username = %s
|
|
""", ('Background Tasks', 'background_tasks', 'inactive', hashed_password, False, old_user[0]))
|
|
logging.info(f"Updated existing '{old_user[0]}' user to 'background_tasks' user.")
|
|
else:
|
|
# Insert new 'background_tasks' user
|
|
cursor.execute("""
|
|
INSERT INTO Users (Fullname, Username, Email, Hashed_PW, IsAdmin)
|
|
VALUES (%s, %s, %s, %s, %s)
|
|
""", ('Background Tasks', 'background_tasks', 'inactive', hashed_password, False))
|
|
logging.info("Inserted new 'background_tasks' user.")
|
|
|
|
|
|
except Exception as e:
|
|
print(f"Error inserting or updating user: {e}")
|
|
logging.error("Error inserting or updating user: %s", e)
|
|
# Rollback the transaction in case of error
|
|
|
|
try:
|
|
# Generate and hash the password
|
|
random_password = generate_random_password()
|
|
hashed_password = hash_password(random_password)
|
|
|
|
if hashed_password:
|
|
insert_or_update_user(cursor, hashed_password)
|
|
|
|
except Exception as e:
|
|
print(f"Error setting default Background Task User: {e}")
|
|
logging.error("Error setting default Background Task User: %s", e)
|
|
|
|
# Create the web Key
|
|
def create_api_key(cnx, user_id=1):
|
|
cursor_key = cnx.cursor()
|
|
|
|
# Check if API key exists for user_id
|
|
query = f"SELECT APIKey FROM APIKeys WHERE UserID = {user_id}"
|
|
cursor_key.execute(query)
|
|
|
|
result = cursor_key.fetchone()
|
|
|
|
if result:
|
|
api_key = result[0]
|
|
else:
|
|
import secrets
|
|
import string
|
|
alphabet = string.ascii_letters + string.digits
|
|
api_key = ''.join(secrets.choice(alphabet) for _ in range(64))
|
|
|
|
# Note the quotes around {api_key}
|
|
query = f"INSERT INTO APIKeys (UserID, APIKey) VALUES ({user_id}, '{api_key}')"
|
|
cursor_key.execute(query)
|
|
|
|
cnx.commit()
|
|
|
|
cursor_key.close()
|
|
return api_key
|
|
|
|
web_api_key = create_api_key(cnx)
|
|
with open("/tmp/web_api_key.txt", "w") as f:
|
|
f.write(web_api_key)
|
|
|
|
# Check if admin environment variables are set
|
|
admin_fullname = os.environ.get("FULLNAME")
|
|
admin_username = os.environ.get("USERNAME")
|
|
admin_email = os.environ.get("EMAIL")
|
|
admin_pw = os.environ.get("PASSWORD")
|
|
|
|
admin_created = False
|
|
if all([admin_fullname, admin_username, admin_email, admin_pw]):
|
|
# Hash the admin password
|
|
hashed_pw = hash_password(admin_pw)
|
|
admin_insert_query = """INSERT IGNORE INTO Users (Fullname, Username, Email, Hashed_PW, IsAdmin)
|
|
VALUES (%s, %s, %s, %s, %s)"""
|
|
# Execute the INSERT statement without a separate salt
|
|
cursor.execute(admin_insert_query, (admin_fullname, admin_username, admin_email, hashed_pw, 1))
|
|
admin_created = True
|
|
|
|
# Always create stats for background_tasks user
|
|
cursor.execute("""INSERT IGNORE INTO UserStats (UserID) VALUES (1)""")
|
|
|
|
# Only create stats for admin if we created the admin user
|
|
if admin_created:
|
|
cursor.execute("""INSERT IGNORE INTO UserStats (UserID) VALUES (2)""")
|
|
|
|
# Create the Podcasts table if it doesn't exist
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS Podcasts (
|
|
PodcastID INT AUTO_INCREMENT PRIMARY KEY,
|
|
PodcastIndexID INT,
|
|
PodcastName TEXT,
|
|
ArtworkURL TEXT,
|
|
Author TEXT,
|
|
Categories TEXT,
|
|
Description TEXT,
|
|
EpisodeCount INT,
|
|
FeedURL TEXT,
|
|
WebsiteURL TEXT,
|
|
Explicit TINYINT(1),
|
|
UserID INT,
|
|
AutoDownload TINYINT(1) DEFAULT 0,
|
|
StartSkip INT DEFAULT 0,
|
|
EndSkip INT DEFAULT 0,
|
|
Username TEXT,
|
|
Password TEXT,
|
|
IsYouTubeChannel TINYINT(1) DEFAULT 0,
|
|
NotificationsEnabled TINYINT(1) DEFAULT 0,
|
|
FeedCutoffDays INT DEFAULT 0,
|
|
PlaybackSpeed DECIMAL(2,1) UNSIGNED DEFAULT 1.0,
|
|
PlaybackSpeedCustomized TINYINT(1) DEFAULT 0,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID)
|
|
)""")
|
|
|
|
def add_youtube_column_if_not_exist(cursor, cnx):
|
|
try:
|
|
# Check if column exists in MySQL
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'Podcasts'
|
|
AND COLUMN_NAME = 'IsYouTubeChannel'
|
|
AND TABLE_SCHEMA = DATABASE()
|
|
""")
|
|
existing_column = cursor.fetchone()
|
|
|
|
if not existing_column:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN IsYouTubeChannel TINYINT(1) DEFAULT 0
|
|
""")
|
|
print("Added 'IsYouTubeChannel' column to 'Podcasts' table.")
|
|
cnx.commit()
|
|
except Exception as e:
|
|
print(f"Error adding IsYouTubeChannel column to Podcasts table: {e}")
|
|
|
|
add_youtube_column_if_not_exist(cursor, cnx)
|
|
|
|
# Function to add PlaybackSpeed to Podcasts table for MySQL
|
|
def add_playbackspeed_if_not_exist_podcasts_mysql(cursor, cnx):
|
|
try:
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'Podcasts'
|
|
AND COLUMN_NAME = 'PlaybackSpeed'
|
|
""")
|
|
existing_column = cursor.fetchone()
|
|
if not existing_column:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN PlaybackSpeed DECIMAL(2,1) UNSIGNED DEFAULT 1.0
|
|
""")
|
|
print("Added 'PlaybackSpeed' column to 'Podcasts' table.")
|
|
cnx.commit()
|
|
else:
|
|
print("Column 'PlaybackSpeed' already exists in 'Podcasts' table.")
|
|
except Exception as e:
|
|
print(f"Error checking PlaybackSpeed column in Podcasts table: {e}")
|
|
|
|
# Function to add PlaybackSpeedCustomized to Podcasts table for MySQL
|
|
def add_playbackspeed_customized_if_not_exist_mysql(cursor, cnx):
|
|
try:
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'Podcasts'
|
|
AND COLUMN_NAME = 'PlaybackSpeedCustomized'
|
|
""")
|
|
existing_column = cursor.fetchone()
|
|
if not existing_column:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN PlaybackSpeedCustomized TINYINT(1) DEFAULT 0
|
|
""")
|
|
print("Added 'PlaybackSpeedCustomized' column to 'Podcasts' table.")
|
|
cnx.commit()
|
|
else:
|
|
print("Column 'PlaybackSpeedCustomized' already exists in 'Podcasts' table.")
|
|
except Exception as e:
|
|
print(f"Error checking PlaybackSpeedCustomized column in Podcasts table: {e}")
|
|
|
|
add_playbackspeed_if_not_exist_podcasts_mysql(cursor, cnx)
|
|
add_playbackspeed_customized_if_not_exist_mysql(cursor, cnx)
|
|
|
|
def add_feed_cutoff_column_if_not_exist(cursor, cnx):
|
|
try:
|
|
# Check if column exists in MySQL
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'Podcasts'
|
|
AND COLUMN_NAME = 'FeedCutoffDays'
|
|
AND TABLE_SCHEMA = DATABASE()
|
|
""")
|
|
existing_column = cursor.fetchone()
|
|
|
|
if not existing_column:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN FeedCutoffDays INT DEFAULT 0
|
|
""")
|
|
print("Added 'FeedCutoffDays' column to 'Podcasts' table.")
|
|
cnx.commit()
|
|
except Exception as e:
|
|
print(f"Error adding FeedCutoffDays column to Podcasts table: {e}")
|
|
|
|
add_feed_cutoff_column_if_not_exist(cursor, cnx)
|
|
|
|
def add_user_pass_columns_if_not_exist(cursor, cnx):
|
|
try:
|
|
# Check if the columns exist
|
|
cursor.execute("""
|
|
SELECT column_name
|
|
FROM information_schema.columns
|
|
WHERE table_name='Podcasts'
|
|
AND column_name IN ('Username', 'Password')
|
|
""")
|
|
existing_columns = cursor.fetchall()
|
|
existing_columns = [col[0] for col in existing_columns]
|
|
|
|
# Add Username column if it doesn't exist
|
|
if 'Username' not in existing_columns:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN Username TEXT
|
|
""")
|
|
print("Added 'Username' column to 'Podcasts' table.")
|
|
|
|
# Add Password column if it doesn't exist
|
|
if 'Password' not in existing_columns:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN Password TEXT
|
|
""")
|
|
print("Added 'Password' column to 'Podcasts' table.")
|
|
|
|
cnx.commit() # Ensure changes are committed
|
|
except Exception as e:
|
|
print(f"Error adding columns to Podcasts table: {e}")
|
|
|
|
# Usage
|
|
add_user_pass_columns_if_not_exist(cursor, cnx)
|
|
|
|
# Check if the new columns exist, and add them if they don't
|
|
cursor.execute("SHOW COLUMNS FROM Podcasts LIKE 'AutoDownload'")
|
|
result = cursor.fetchone()
|
|
if not result:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN AutoDownload TINYINT(1) DEFAULT 0,
|
|
ADD COLUMN StartSkip INT DEFAULT 0,
|
|
ADD COLUMN EndSkip INT DEFAULT 0
|
|
""")
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS Episodes (
|
|
EpisodeID INT AUTO_INCREMENT PRIMARY KEY,
|
|
PodcastID INT,
|
|
EpisodeTitle TEXT,
|
|
EpisodeDescription TEXT,
|
|
EpisodeURL TEXT,
|
|
EpisodeArtwork TEXT,
|
|
EpisodePubDate DATETIME,
|
|
EpisodeDuration INT,
|
|
Completed TINYINT(1) DEFAULT 0,
|
|
FOREIGN KEY (PodcastID) REFERENCES Podcasts(PodcastID)
|
|
)""")
|
|
# Check if the Completed column exists, and add it if it doesn't
|
|
cursor.execute("SHOW COLUMNS FROM Episodes LIKE 'Completed'")
|
|
result = cursor.fetchone()
|
|
if not result:
|
|
cursor.execute("""
|
|
ALTER TABLE Episodes
|
|
ADD COLUMN Completed TINYINT(1) DEFAULT 0
|
|
""")
|
|
|
|
try:
|
|
# YouTubeVideos table
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS YouTubeVideos (
|
|
VideoID INT AUTO_INCREMENT PRIMARY KEY,
|
|
PodcastID INT,
|
|
VideoTitle TEXT,
|
|
VideoDescription TEXT,
|
|
VideoURL TEXT,
|
|
ThumbnailURL TEXT,
|
|
PublishedAt TIMESTAMP,
|
|
Duration INT,
|
|
YouTubeVideoID TEXT,
|
|
Completed TINYINT(1) DEFAULT 0,
|
|
ListenPosition INT DEFAULT 0,
|
|
FOREIGN KEY (PodcastID) REFERENCES Podcasts(PodcastID)
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
|
|
except Exception as e:
|
|
print(f"Error creating YoutubeVideos Table: {e}")
|
|
|
|
|
|
def create_index_if_not_exists(cursor, index_name, table_name, column_name):
|
|
cursor.execute(f"SELECT COUNT(1) IndexIsThere FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = DATABASE() AND index_name = '{index_name}'")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute(f"CREATE INDEX {index_name} ON {table_name}({column_name})")
|
|
|
|
create_index_if_not_exists(cursor, "idx_podcasts_userid", "Podcasts", "UserID")
|
|
create_index_if_not_exists(cursor, "idx_episodes_podcastid", "Episodes", "PodcastID")
|
|
create_index_if_not_exists(cursor, "idx_episodes_episodepubdate", "Episodes", "EpisodePubDate")
|
|
|
|
|
|
try:
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS People (
|
|
PersonID INT AUTO_INCREMENT PRIMARY KEY,
|
|
Name TEXT,
|
|
PersonImg TEXT,
|
|
PeopleDBID INT,
|
|
AssociatedPodcasts TEXT,
|
|
UserID INT,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID)
|
|
);
|
|
""")
|
|
cnx.commit()
|
|
except Exception as e:
|
|
print(f"Error creating People table: {e}")
|
|
|
|
try:
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS PeopleEpisodes (
|
|
EpisodeID INT AUTO_INCREMENT PRIMARY KEY,
|
|
PersonID INT,
|
|
PodcastID INT,
|
|
EpisodeTitle TEXT,
|
|
EpisodeDescription TEXT,
|
|
EpisodeURL TEXT,
|
|
EpisodeArtwork TEXT,
|
|
EpisodePubDate DATETIME,
|
|
EpisodeDuration INT,
|
|
AddedDate DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (PersonID) REFERENCES People(PersonID),
|
|
FOREIGN KEY (PodcastID) REFERENCES Podcasts(PodcastID)
|
|
);
|
|
""")
|
|
cnx.commit()
|
|
except Exception as e:
|
|
print(f"Error creating PeopleEpisodes table: {e}")
|
|
|
|
create_index_if_not_exists(cursor, "idx_people_episodes_person", "PeopleEpisodes", "PersonID")
|
|
create_index_if_not_exists(cursor, "idx_people_episodes_podcast", "PeopleEpisodes", "PodcastID")
|
|
|
|
|
|
try:
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS SharedEpisodes (
|
|
SharedEpisodeID INT AUTO_INCREMENT PRIMARY KEY,
|
|
EpisodeID INT,
|
|
UrlKey TEXT,
|
|
ExpirationDate DATETIME,
|
|
FOREIGN KEY (EpisodeID) REFERENCES Episodes(EpisodeID)
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
except Exception as e:
|
|
print(f"Error creating SharedEpisodes table: {e}")
|
|
|
|
|
|
|
|
try:
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS UserSettings (
|
|
UserSettingID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT UNIQUE,
|
|
Theme VARCHAR(255) DEFAULT 'Nordic',
|
|
StartPage VARCHAR(255) DEFAULT 'home',
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID)
|
|
)""")
|
|
except Exception as e:
|
|
print(f"Error adding UserSettings table: {e}")
|
|
|
|
def add_startpage_column():
|
|
try:
|
|
# Check if the column exists
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME='UserSettings'
|
|
AND COLUMN_NAME='StartPage'
|
|
AND TABLE_SCHEMA=DATABASE();
|
|
""")
|
|
|
|
# If the column doesn't exist (no rows returned), add it
|
|
if not cursor.fetchone():
|
|
cursor.execute("""
|
|
ALTER TABLE UserSettings
|
|
ADD COLUMN StartPage VARCHAR(255) DEFAULT 'home';
|
|
""")
|
|
print("Successfully added StartPage column to UserSettings table")
|
|
else:
|
|
print("StartPage column already exists in UserSettings table")
|
|
|
|
except Exception as e:
|
|
print(f"Error adding StartPage column: {e}")
|
|
|
|
# Call the function to ensure the column exists
|
|
add_startpage_column()
|
|
|
|
cursor.execute("""INSERT IGNORE INTO UserSettings (UserID, Theme) VALUES ('1', 'Nordic')""")
|
|
cursor.execute("""INSERT IGNORE INTO UserSettings (UserID, Theme) VALUES ('2', 'Nordic')""")
|
|
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS UserEpisodeHistory (
|
|
UserEpisodeHistoryID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
EpisodeID INT,
|
|
ListenDate DATETIME,
|
|
ListenDuration INT,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
FOREIGN KEY (EpisodeID) REFERENCES Episodes(EpisodeID)
|
|
)""")
|
|
|
|
try:
|
|
# UserVideoHistory table
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS UserVideoHistory (
|
|
UserVideoHistoryID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
VideoID INT,
|
|
ListenDate TIMESTAMP,
|
|
ListenDuration INT DEFAULT 0,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
FOREIGN KEY (VideoID) REFERENCES YouTubeVideos(VideoID)
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
|
|
except Exception as e:
|
|
print(f"Error creating UserVideoHistory table: {e}")
|
|
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS SavedEpisodes (
|
|
SaveID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
EpisodeID INT,
|
|
SaveDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
FOREIGN KEY (EpisodeID) REFERENCES Episodes(EpisodeID)
|
|
)""")
|
|
|
|
try:
|
|
# SavedVideos table
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS SavedVideos (
|
|
SaveID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
VideoID INT,
|
|
SaveDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
FOREIGN KEY (VideoID) REFERENCES YouTubeVideos(VideoID)
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
|
|
except Exception as e:
|
|
print(f"Error creating SavedVideos table: {e}")
|
|
|
|
|
|
# Create the DownloadedEpisodes table
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS DownloadedEpisodes (
|
|
DownloadID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
EpisodeID INT,
|
|
DownloadedDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
DownloadedSize INT,
|
|
DownloadedLocation VARCHAR(255),
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
FOREIGN KEY (EpisodeID) REFERENCES Episodes(EpisodeID)
|
|
)""")
|
|
|
|
try:
|
|
# DownloadedVideos table
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS DownloadedVideos (
|
|
DownloadID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
VideoID INT,
|
|
DownloadedDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
DownloadedSize INT,
|
|
DownloadedLocation VARCHAR(255),
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
FOREIGN KEY (VideoID) REFERENCES YouTubeVideos(VideoID)
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
|
|
except Exception as e:
|
|
print(f"Error creating DownloadedVideos table: {e}")
|
|
|
|
# Create the EpisodeQueue table
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS EpisodeQueue (
|
|
QueueID INT AUTO_INCREMENT PRIMARY KEY,
|
|
QueueDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UserID INT,
|
|
EpisodeID INT,
|
|
QueuePosition INT NOT NULL DEFAULT 0,
|
|
is_youtube TINYINT(1) DEFAULT 0,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
FOREIGN KEY (EpisodeID) REFERENCES Episodes(EpisodeID)
|
|
)""")
|
|
|
|
def add_queue_youtube_column_if_not_exist(cursor, cnx):
|
|
try:
|
|
# Check if column exists in MySQL
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'EpisodeQueue'
|
|
AND COLUMN_NAME = 'is_youtube'
|
|
AND TABLE_SCHEMA = DATABASE()
|
|
""")
|
|
existing_column = cursor.fetchone()
|
|
|
|
if not existing_column:
|
|
try:
|
|
# Add the is_youtube column
|
|
cursor.execute("""
|
|
ALTER TABLE EpisodeQueue
|
|
ADD COLUMN is_youtube TINYINT(1) DEFAULT 0
|
|
""")
|
|
cnx.commit()
|
|
print("Added 'is_youtube' column to 'EpisodeQueue' table.")
|
|
except Exception as e:
|
|
cnx.rollback()
|
|
if 'Duplicate column name' not in str(e): # MySQL specific error message
|
|
print(f"Error adding is_youtube column to EpisodeQueue table: {e}")
|
|
else:
|
|
cnx.commit() # Commit transaction even if column exists
|
|
|
|
except Exception as e:
|
|
cnx.rollback()
|
|
print(f"Error checking for is_youtube column: {e}")
|
|
|
|
add_queue_youtube_column_if_not_exist(cursor, cnx)
|
|
|
|
def add_rssonly_column_if_not_exists(cursor, cnx):
|
|
try:
|
|
# Check if column exists in MySQL
|
|
cursor.execute("""
|
|
SELECT COLUMN_NAME
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'APIKeys'
|
|
AND COLUMN_NAME = 'RssOnly'
|
|
AND TABLE_SCHEMA = DATABASE()
|
|
""")
|
|
existing_column = cursor.fetchone()
|
|
|
|
if not existing_column:
|
|
try:
|
|
# Add the is_youtube column
|
|
cursor.execute("""
|
|
ALTER TABLE APIKeys
|
|
ADD COLUMN RssOnly TINYINT(1) DEFAULT 0
|
|
""")
|
|
cnx.commit()
|
|
print("Added 'RssOnly' column to 'APIKeys' table.")
|
|
except Exception as e:
|
|
cnx.rollback()
|
|
if 'Duplicate column name' not in str(e): # MySQL specific error message
|
|
print(f"Error adding RssOnly column to APIKeys table: {e}")
|
|
else:
|
|
cnx.commit() # Commit transaction even if column exists
|
|
|
|
except Exception as e:
|
|
cnx.rollback()
|
|
print(f"Error checking for is_youtube column: {e}")
|
|
|
|
add_rssonly_column_if_not_exists(cursor, cnx)
|
|
|
|
# Create the Sessions table
|
|
cursor.execute("""CREATE TABLE IF NOT EXISTS Sessions (
|
|
SessionID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
value TEXT,
|
|
expire DATETIME NOT NULL,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID)
|
|
)""")
|
|
|
|
def add_notification_column_if_not_exists(cursor, cnx):
|
|
try:
|
|
# First check if the column exists
|
|
cursor.execute("""
|
|
SELECT COUNT(*) as column_exists
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'Podcasts'
|
|
AND COLUMN_NAME = 'NotificationsEnabled'
|
|
AND TABLE_SCHEMA = DATABASE()
|
|
""")
|
|
result = cursor.fetchone()
|
|
column_exists = result[0] > 0 if isinstance(result, tuple) else result.get('column_exists', 0) > 0
|
|
|
|
# Only attempt to add the column if it doesn't exist
|
|
if not column_exists:
|
|
try:
|
|
cursor.execute("""
|
|
ALTER TABLE Podcasts
|
|
ADD COLUMN NotificationsEnabled TINYINT(1) DEFAULT 0
|
|
""")
|
|
print("Added NotificationsEnabled column to Podcasts table.")
|
|
cnx.commit()
|
|
except Exception as alter_err:
|
|
# Check if the error is because the column already exists
|
|
# (This can happen in race conditions or if the schema check was outdated)
|
|
if "Duplicate column name" in str(alter_err) or "column already exists" in str(alter_err).lower():
|
|
print("Column NotificationsEnabled already exists in Podcasts table.")
|
|
else:
|
|
# It's a different error, so re-raise it
|
|
raise alter_err
|
|
else:
|
|
print("Column NotificationsEnabled already exists in Podcasts table.")
|
|
|
|
except Exception as e:
|
|
print(f"Error checking/adding NotificationsEnabled column to Podcasts table: {e}")
|
|
# Only rollback if we're in a transaction that needs rolling back
|
|
try:
|
|
cnx.rollback()
|
|
except:
|
|
pass # If rollback fails, we're not in a transaction
|
|
|
|
# Create the notification settings table
|
|
try:
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS UserNotificationSettings (
|
|
SettingID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT,
|
|
Platform VARCHAR(50) NOT NULL,
|
|
Enabled TINYINT(1) DEFAULT 1,
|
|
NtfyTopic VARCHAR(255),
|
|
NtfyServerUrl VARCHAR(255) DEFAULT 'https://ntfy.sh',
|
|
GotifyUrl VARCHAR(255),
|
|
GotifyToken VARCHAR(255),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID),
|
|
UNIQUE(UserID, platform)
|
|
)
|
|
""")
|
|
print("Checked/Created UserNotificationSettings table")
|
|
cnx.commit()
|
|
except Exception as e:
|
|
print(f"Error creating UserNotificationSettings table: {e}")
|
|
cnx.rollback()
|
|
|
|
# Call our function to add the new column
|
|
add_notification_column_if_not_exists(cursor, cnx)
|
|
|
|
try:
|
|
# Create Playlists table with the unique constraint
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS Playlists (
|
|
PlaylistID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
Name VARCHAR(255) NOT NULL,
|
|
Description TEXT,
|
|
IsSystemPlaylist TINYINT(1) NOT NULL DEFAULT 0,
|
|
PodcastIDs TEXT, -- Storing as JSON array in MySQL
|
|
IncludeUnplayed TINYINT(1) NOT NULL DEFAULT 1,
|
|
IncludePartiallyPlayed TINYINT(1) NOT NULL DEFAULT 1,
|
|
IncludePlayed TINYINT(1) NOT NULL DEFAULT 0,
|
|
MinDuration INT, -- NULL means no minimum
|
|
MaxDuration INT, -- NULL means no maximum
|
|
SortOrder VARCHAR(50) NOT NULL DEFAULT 'date_desc',
|
|
GroupByPodcast TINYINT(1) NOT NULL DEFAULT 0,
|
|
MaxEpisodes INT, -- NULL means no limit
|
|
PlayProgressMin FLOAT, -- NULL means no minimum progress requirement
|
|
PlayProgressMax FLOAT, -- NULL means no maximum progress limit
|
|
TimeFilterHours INT, -- NULL means no time filter
|
|
LastUpdated DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
Created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
IconName VARCHAR(50) NOT NULL DEFAULT 'ph-playlist',
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, Name),
|
|
CHECK (PlayProgressMin IS NULL OR (PlayProgressMin >= 0 AND PlayProgressMin <= 100)),
|
|
CHECK (PlayProgressMax IS NULL OR (PlayProgressMax >= 0 AND PlayProgressMax <= 100)),
|
|
CHECK (PlayProgressMin IS NULL OR PlayProgressMax IS NULL OR PlayProgressMin <= PlayProgressMax),
|
|
CHECK (MinDuration IS NULL OR MinDuration >= 0),
|
|
CHECK (MaxDuration IS NULL OR MaxDuration >= 0),
|
|
CHECK (MinDuration IS NULL OR MaxDuration IS NULL OR MinDuration <= MaxDuration),
|
|
CHECK (TimeFilterHours IS NULL OR TimeFilterHours > 0),
|
|
CHECK (MaxEpisodes IS NULL OR MaxEpisodes > 0),
|
|
CHECK (SortOrder IN ('date_asc', 'date_desc',
|
|
'duration_asc', 'duration_desc',
|
|
'listen_progress', 'completion'))
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
|
|
# Create PlaylistContents table
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS PlaylistContents (
|
|
PlaylistContentID INT AUTO_INCREMENT PRIMARY KEY,
|
|
PlaylistID INT,
|
|
EpisodeID INT,
|
|
VideoID INT,
|
|
Position INT,
|
|
DateAdded DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (PlaylistID) REFERENCES Playlists(PlaylistID) ON DELETE CASCADE,
|
|
FOREIGN KEY (EpisodeID) REFERENCES Episodes(EpisodeID) ON DELETE CASCADE,
|
|
FOREIGN KEY (VideoID) REFERENCES YouTubeVideos(VideoID) ON DELETE CASCADE,
|
|
CHECK ((EpisodeID IS NOT NULL AND VideoID IS NULL) OR (EpisodeID IS NULL AND VideoID IS NOT NULL))
|
|
)
|
|
""")
|
|
cnx.commit()
|
|
|
|
# Create indexes - check if they exist first
|
|
# Index 1: idx_playlists_userid
|
|
cursor.execute("""
|
|
SELECT COUNT(1) IndexExists FROM INFORMATION_SCHEMA.STATISTICS
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'Playlists'
|
|
AND index_name = 'idx_playlists_userid'
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("CREATE INDEX idx_playlists_userid ON Playlists(UserID)")
|
|
cnx.commit()
|
|
print("Created index idx_playlists_userid")
|
|
|
|
# Index 2: idx_playlist_contents_playlistid
|
|
cursor.execute("""
|
|
SELECT COUNT(1) IndexExists FROM INFORMATION_SCHEMA.STATISTICS
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'PlaylistContents'
|
|
AND index_name = 'idx_playlist_contents_playlistid'
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("CREATE INDEX idx_playlist_contents_playlistid ON PlaylistContents(PlaylistID)")
|
|
cnx.commit()
|
|
print("Created index idx_playlist_contents_playlistid")
|
|
|
|
# Index 3: idx_playlist_contents_episodeid
|
|
cursor.execute("""
|
|
SELECT COUNT(1) IndexExists FROM INFORMATION_SCHEMA.STATISTICS
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'PlaylistContents'
|
|
AND index_name = 'idx_playlist_contents_episodeid'
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("CREATE INDEX idx_playlist_contents_episodeid ON PlaylistContents(EpisodeID)")
|
|
cnx.commit()
|
|
print("Created index idx_playlist_contents_episodeid")
|
|
|
|
# Index 4: idx_playlist_contents_videoid
|
|
cursor.execute("""
|
|
SELECT COUNT(1) IndexExists FROM INFORMATION_SCHEMA.STATISTICS
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'PlaylistContents'
|
|
AND index_name = 'idx_playlist_contents_videoid'
|
|
""")
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("CREATE INDEX idx_playlist_contents_videoid ON PlaylistContents(VideoID)")
|
|
cnx.commit()
|
|
print("Created index idx_playlist_contents_videoid")
|
|
|
|
# Define system playlists
|
|
system_playlists = [
|
|
{
|
|
'name': 'Quick Listens',
|
|
'description': 'Short episodes under 15 minutes, perfect for quick breaks',
|
|
'min_duration': None,
|
|
'max_duration': 900, # 15 minutes
|
|
'sort_order': 'duration_asc',
|
|
'icon_name': 'ph-fast-forward'
|
|
},
|
|
{
|
|
'name': 'Longform',
|
|
'description': 'Extended episodes over 1 hour, ideal for long drives or deep dives',
|
|
'min_duration': 3600, # 1 hour
|
|
'max_duration': None,
|
|
'sort_order': 'duration_desc',
|
|
'icon_name': 'ph-car'
|
|
},
|
|
{
|
|
'name': 'Currently Listening',
|
|
'description': 'Episodes you\'ve started but haven\'t finished',
|
|
'min_duration': None,
|
|
'max_duration': None,
|
|
'sort_order': 'date_desc',
|
|
'include_unplayed': False,
|
|
'include_partially_played': True,
|
|
'include_played': False,
|
|
'icon_name': 'ph-play'
|
|
},
|
|
{
|
|
'name': 'Fresh Releases',
|
|
'description': 'Latest episodes from the last 24 hours',
|
|
'min_duration': None,
|
|
'max_duration': None,
|
|
'sort_order': 'date_desc',
|
|
'include_unplayed': True,
|
|
'include_partially_played': False,
|
|
'include_played': False,
|
|
'time_filter_hours': 24,
|
|
'icon_name': 'ph-sparkle'
|
|
},
|
|
{
|
|
'name': 'Weekend Marathon',
|
|
'description': 'Longer episodes (30+ minutes) perfect for weekend listening',
|
|
'min_duration': 1800, # 30 minutes
|
|
'max_duration': None,
|
|
'sort_order': 'duration_desc',
|
|
'group_by_podcast': True,
|
|
'icon_name': 'ph-couch'
|
|
},
|
|
{
|
|
'name': 'Commuter Mix',
|
|
'description': 'Episodes between 20-40 minutes, ideal for average commute times',
|
|
'min_duration': 1200, # 20 minutes
|
|
'max_duration': 2400, # 40 minutes
|
|
'sort_order': 'date_desc',
|
|
'icon_name': 'ph-train'
|
|
},
|
|
{
|
|
'name': 'Almost Done',
|
|
'description': 'Episodes you\'re close to finishing (75%+ complete)',
|
|
'min_duration': None,
|
|
'max_duration': None,
|
|
'sort_order': 'date_asc',
|
|
'include_unplayed': False,
|
|
'include_partially_played': True,
|
|
'include_played': False,
|
|
'play_progress_min': 75.0,
|
|
'play_progress_max': None,
|
|
'icon_name': 'ph-hourglass'
|
|
}
|
|
]
|
|
|
|
# Insert system playlists
|
|
for playlist in system_playlists:
|
|
try:
|
|
# First check if this playlist already exists
|
|
cursor.execute("""
|
|
SELECT COUNT(*)
|
|
FROM Playlists
|
|
WHERE UserID = 1 AND Name = %s AND IsSystemPlaylist = 1
|
|
""", (playlist['name'],))
|
|
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute("""
|
|
INSERT INTO Playlists (
|
|
UserID,
|
|
Name,
|
|
Description,
|
|
IsSystemPlaylist,
|
|
MinDuration,
|
|
MaxDuration,
|
|
SortOrder,
|
|
GroupByPodcast,
|
|
IncludeUnplayed,
|
|
IncludePartiallyPlayed,
|
|
IncludePlayed,
|
|
IconName,
|
|
PlayProgressMin,
|
|
PlayProgressMax,
|
|
TimeFilterHours
|
|
) VALUES (
|
|
1,
|
|
%s,
|
|
%s,
|
|
1,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s,
|
|
%s
|
|
)
|
|
""", (
|
|
playlist['name'],
|
|
playlist['description'],
|
|
playlist.get('min_duration'),
|
|
playlist.get('max_duration'),
|
|
playlist.get('sort_order', 'date_asc'),
|
|
1 if playlist.get('group_by_podcast', False) else 0,
|
|
1 if playlist.get('include_unplayed', True) else 0,
|
|
1 if playlist.get('include_partially_played', True) else 0,
|
|
1 if playlist.get('include_played', False) else 0,
|
|
playlist.get('icon_name', 'ph-playlist'),
|
|
playlist.get('play_progress_min'),
|
|
playlist.get('play_progress_max'),
|
|
playlist.get('time_filter_hours')
|
|
))
|
|
cnx.commit()
|
|
print(f"Successfully added system playlist: {playlist['name']}")
|
|
else:
|
|
print(f"System playlist already exists: {playlist['name']}")
|
|
|
|
except Exception as e:
|
|
print(f"Error handling system playlist {playlist['name']}: {e}")
|
|
continue
|
|
|
|
except Exception as e:
|
|
print(f"Error setting up platlists: {e}")
|
|
|
|
print("Checked/Created Playlist Tables")
|
|
|
|
except mysql.connector.Error as err:
|
|
logging.error(f"Database error: {err}")
|
|
except Exception as e:
|
|
logging.error(f"General error: {e}")
|
|
|
|
# Ensure to close the cursor and connection
|
|
finally:
|
|
if 'cursor' in locals():
|
|
cursor.close()
|
|
if 'cnx' in locals():
|
|
cnx.close()
|