From 61db8d88e50d566c08b34fcafe129efe0b2c89b7 Mon Sep 17 00:00:00 2001 From: Ole Valente Date: Fri, 10 Jul 2026 23:04:37 +0200 Subject: [PATCH] Fix .gitignore: models/ was excluding backend/app/models/ --- .gitignore | 2 +- backend/app/models/__init__.py | 0 backend/app/models/database.py | 24 +++++++++ backend/app/models/schemas.py | 64 ++++++++++++++++++++++++ backend/app/models/tables.py | 90 ++++++++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 backend/app/models/__init__.py create mode 100644 backend/app/models/database.py create mode 100644 backend/app/models/schemas.py create mode 100644 backend/app/models/tables.py diff --git a/.gitignore b/.gitignore index 89cfac5..bf6083e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ data/ -models/ +/models/ .env __pycache__/ *.pyc diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/database.py b/backend/app/models/database.py new file mode 100644 index 0000000..176bb01 --- /dev/null +++ b/backend/app/models/database.py @@ -0,0 +1,24 @@ +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession +from sqlalchemy.orm import DeclarativeBase +import os + +DATABASE_URL = os.getenv("DATABASE_URL", "sqlite+aiosqlite:////data/app/bornetime.db") + +engine = create_async_engine(DATABASE_URL, echo=False) +async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + + +class Base(DeclarativeBase): + pass + + +async def init_db(): + async with engine.begin() as conn: + from .tables import Video, Segment, Job + + await conn.run_sync(Base.metadata.create_all) + + +async def get_session(): + async with async_session() as session: + yield session diff --git a/backend/app/models/schemas.py b/backend/app/models/schemas.py new file mode 100644 index 0000000..1d77ff5 --- /dev/null +++ b/backend/app/models/schemas.py @@ -0,0 +1,64 @@ +from pydantic import BaseModel, Field +from typing import Optional +import datetime + + +class VideoOut(BaseModel): + id: int + filename: str + duration: Optional[float] = None + status: str + output_path: Optional[str] = None + error_message: Optional[str] = None + created_at: datetime.datetime + + model_config = {"from_attributes": True} + + +class VideoListOut(BaseModel): + videos: list[VideoOut] + + +class SegmentOut(BaseModel): + id: int + video_id: int + speaker_label: Optional[str] = None + start_time: float + end_time: float + english_text: Optional[str] = None + danish_text: Optional[str] = None + status: str + + model_config = {"from_attributes": True} + + +class SegmentUpdate(BaseModel): + english_text: Optional[str] = None + danish_text: Optional[str] = None + speaker_label: Optional[str] = None + + +class TranscriptOut(BaseModel): + video_id: int + segments: list[SegmentOut] + + +class JobOut(BaseModel): + id: int + video_id: int + job_type: str + status: str + progress: float + error_message: Optional[str] = None + created_at: datetime.datetime + + model_config = {"from_attributes": True} + + +class JobListOut(BaseModel): + jobs: list[JobOut] + + +class ProcessResponse(BaseModel): + job_id: int + message: str diff --git a/backend/app/models/tables.py b/backend/app/models/tables.py new file mode 100644 index 0000000..8bf9548 --- /dev/null +++ b/backend/app/models/tables.py @@ -0,0 +1,90 @@ +import datetime +from sqlalchemy import ( + Column, + Integer, + String, + Float, + Text, + DateTime, + ForeignKey, + Enum as SAEnum, +) +from sqlalchemy.orm import relationship +from .database import Base +import enum + + +class VideoStatus(str, enum.Enum): + uploaded = "uploaded" + queued = "queued" + processing = "processing" + review = "review" + generating = "generating" + done = "done" + error = "error" + + +class JobType(str, enum.Enum): + process = "process" + generate = "generate" + + +class JobStatus(str, enum.Enum): + pending = "pending" + running = "running" + done = "done" + failed = "failed" + + +class Video(Base): + __tablename__ = "videos" + + id = Column(Integer, primary_key=True, autoincrement=True) + filename = Column(String(512), nullable=False) + filepath = Column(String(1024), nullable=False) + duration = Column(Float, nullable=True) + status = Column(String(32), default=VideoStatus.uploaded.value) + output_path = Column(String(1024), nullable=True) + error_message = Column(Text, nullable=True) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + updated_at = Column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) + + segments = relationship( + "Segment", back_populates="video", cascade="all, delete-orphan" + ) + jobs = relationship("Job", back_populates="video", cascade="all, delete-orphan") + + +class Segment(Base): + __tablename__ = "segments" + + id = Column(Integer, primary_key=True, autoincrement=True) + video_id = Column(Integer, ForeignKey("videos.id"), nullable=False) + speaker_label = Column(String(32), nullable=True) + start_time = Column(Float, nullable=False) + end_time = Column(Float, nullable=False) + english_text = Column(Text, nullable=True) + danish_text = Column(Text, nullable=True) + status = Column(String(32), default="pending") + + video = relationship("Video", back_populates="segments") + + +class Job(Base): + __tablename__ = "jobs" + + id = Column(Integer, primary_key=True, autoincrement=True) + video_id = Column(Integer, ForeignKey("videos.id"), nullable=False) + job_type = Column(String(32), nullable=False) + status = Column(String(32), default=JobStatus.pending.value) + progress = Column(Float, default=0.0) + error_message = Column(Text, nullable=True) + celery_task_id = Column(String(256), nullable=True) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + updated_at = Column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) + + video = relationship("Video", back_populates="jobs")