Files
radiosync/core/database.py
T
karol 76f5d30db1 Initial commit
Co-authored-by: GitHub Copilot <noreply@github.com>
2026-09-03 11:57:16 +02:00

111 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from sqlalchemy import create_engine, Column, String, Integer, Boolean, Float, Text, DateTime, TypeDecorator
from sqlalchemy.orm import declarative_base, sessionmaker
from .security import encrypt_config_value, decrypt_config_value
DATABASE_URL = "sqlite:///./data/radiosync.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class EncryptedString(TypeDecorator):
impl = String
cache_ok = True
def process_bind_param(self, value, dialect):
return encrypt_config_value(value)
def process_result_value(self, value, dialect):
return decrypt_config_value(value)
class Config(Base):
__tablename__ = "config"
key = Column(String, primary_key=True, index=True)
value = Column(EncryptedString)
class Program(Base):
__tablename__ = "programs"
id = Column(Integer, primary_key=True, index=True)
station = Column(String, index=True) # "radio357", "rns", "jazz"
slug = Column(String, index=True)
name = Column(String)
description = Column(Text, nullable=True)
image = Column(String, nullable=True)
# Sync tracking
backfill_page = Column(Integer, default=1)
backfill_complete = Column(Boolean, default=False)
total_pages = Column(Integer, default=0)
last_catchup = Column(Float, default=0.0)
next_catchup_after = Column(Float, default=0.0) # timestamp – nie sprawdzaj przed tym czasem
class Episode(Base):
__tablename__ = "episodes"
id = Column(Integer, primary_key=True, index=True)
station = Column(String, index=True)
program_slug = Column(String, index=True)
ep_id = Column(String, index=True) # ID from the radio API
title = Column(String)
authors = Column(String, nullable=True)
url = Column(String, nullable=True)
image = Column(String, nullable=True)
pub_date = Column(String, nullable=True) # ISO format or YYYY-MM-DD
duration_secs = Column(Integer, default=0)
description = Column(Text, nullable=True)
is_broken = Column(Boolean, default=False)
class ErrorEvent(Base):
__tablename__ = "error_events"
id = Column(Integer, primary_key=True, index=True)
station = Column(String, index=True, nullable=False)
source = Column(String, nullable=False)
message = Column(Text, nullable=False)
severity = Column(String, default="error", nullable=False)
fingerprint = Column(String, index=True, nullable=False)
occurrences = Column(Integer, default=1, nullable=False)
first_seen = Column(DateTime, nullable=False)
last_seen = Column(DateTime, nullable=False)
acknowledged = Column(Boolean, default=False, nullable=False)
acknowledged_at = Column(DateTime, nullable=True)
snapshot_path = Column(String, nullable=True)
def init_db():
Base.metadata.create_all(bind=engine)
import sqlite3, os
db_path = DATABASE_URL.replace("sqlite:///", "")
if os.path.exists(db_path):
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
existing_columns = {
row[1] for row in cursor.execute("PRAGMA table_info(programs)")
}
for col, typedef in [
("next_catchup_after", "REAL DEFAULT 0.0"),
("total_pages", "INTEGER DEFAULT 0"),
]:
if col not in existing_columns:
cursor.execute(f"ALTER TABLE programs ADD COLUMN {col} {typedef}")
error_columns = {row[1] for row in cursor.execute("PRAGMA table_info(error_events)")}
if "snapshot_path" not in error_columns:
cursor.execute("ALTER TABLE error_events ADD COLUMN snapshot_path TEXT")
# Encrypt legacy plaintext values using the raw SQLite connection;
# assigning the same decrypted value through the ORM is not dirty.
rows = cursor.execute("SELECT key, value FROM config").fetchall()
for key, value in rows:
if value is not None and not value.startswith("enc:v1:"):
cursor.execute("UPDATE config SET value = ? WHERE key = ?",
(encrypt_config_value(value), key))
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()