feat: add database models and alembic setup
- SQLAlchemy async models for sources, jobs, schedules, executions, backups, settings - Alembic migration configuration
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
[alembic]
|
||||||
|
script_location = alembic
|
||||||
|
prepend_sys_path = .
|
||||||
|
version_path_separator = os
|
||||||
|
sqlalchemy.url = sqlite+aiosqlite:///./backup_tool.db
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Generic single-database configuration.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import asyncio
|
||||||
|
from logging.config import fileConfig
|
||||||
|
from sqlalchemy import pool
|
||||||
|
from sqlalchemy.engine import Connection
|
||||||
|
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||||
|
from alembic import context
|
||||||
|
from app.models import Base
|
||||||
|
|
||||||
|
config = context.config
|
||||||
|
if config.config_file_name is not None:
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
|
def run_migrations_offline() -> None:
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
def do_run_migrations(connection: Connection) -> None:
|
||||||
|
context.configure(connection=connection, target_metadata=target_metadata)
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
async def run_async_migrations() -> None:
|
||||||
|
connectable = async_engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section, {}),
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
async with connectable.connect() as connection:
|
||||||
|
await connection.run_sync(do_run_migrations)
|
||||||
|
await connectable.dispose()
|
||||||
|
|
||||||
|
def run_migrations_online() -> None:
|
||||||
|
asyncio.run(run_async_migrations())
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = ${repr(up_revision)}
|
||||||
|
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||||
|
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||||
|
from sqlalchemy.orm import declarative_base
|
||||||
|
|
||||||
|
DATABASE_URL = "sqlite+aiosqlite:///./backup_tool.db"
|
||||||
|
|
||||||
|
engine = create_async_engine(DATABASE_URL, echo=True)
|
||||||
|
AsyncSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
async def get_db():
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
try:
|
||||||
|
yield session
|
||||||
|
finally:
|
||||||
|
await session.close()
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, ForeignKey, JSON
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from datetime import datetime
|
||||||
|
from .database import Base
|
||||||
|
|
||||||
|
class Source(Base):
|
||||||
|
__tablename__ = "sources"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
type = Column(String, nullable=False) # local, ssh, database
|
||||||
|
config = Column(JSON, default=dict)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
jobs = relationship("Job", back_populates="source", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
class Job(Base):
|
||||||
|
__tablename__ = "jobs"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
source_id = Column(Integer, ForeignKey("sources.id"), nullable=False)
|
||||||
|
strategy = Column(String, nullable=False, default="full") # full, incremental
|
||||||
|
destination_path = Column(String, nullable=False)
|
||||||
|
exclude_patterns = Column(JSON, default=list)
|
||||||
|
enabled = Column(Boolean, default=True)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
source = relationship("Source", back_populates="jobs")
|
||||||
|
schedule = relationship("Schedule", back_populates="job", uselist=False, cascade="all, delete-orphan")
|
||||||
|
executions = relationship("JobExecution", back_populates="job", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
class Schedule(Base):
|
||||||
|
__tablename__ = "schedules"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
job_id = Column(Integer, ForeignKey("jobs.id"), unique=True, nullable=False)
|
||||||
|
cron_expression = Column(String, nullable=False)
|
||||||
|
enabled = Column(Boolean, default=True)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
job = relationship("Job", back_populates="schedule")
|
||||||
|
|
||||||
|
class JobExecution(Base):
|
||||||
|
__tablename__ = "job_executions"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
job_id = Column(Integer, ForeignKey("jobs.id"), nullable=False)
|
||||||
|
status = Column(String, nullable=False, default="pending") # pending, running, success, failed, cancelled
|
||||||
|
started_at = Column(DateTime, nullable=True)
|
||||||
|
completed_at = Column(DateTime, nullable=True)
|
||||||
|
bytes_processed = Column(Integer, default=0)
|
||||||
|
bytes_backed_up = Column(Integer, default=0)
|
||||||
|
error_message = Column(Text, nullable=True)
|
||||||
|
triggered_by = Column(String, nullable=False) # manual, schedule
|
||||||
|
|
||||||
|
job = relationship("Job", back_populates="executions")
|
||||||
|
backups = relationship("Backup", back_populates="execution", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
class Backup(Base):
|
||||||
|
__tablename__ = "backups"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
execution_id = Column(Integer, ForeignKey("job_executions.id"), nullable=False)
|
||||||
|
storage_path = Column(String, nullable=False)
|
||||||
|
size_bytes = Column(Integer, default=0)
|
||||||
|
checksum = Column(String, nullable=True)
|
||||||
|
type = Column(String, nullable=False) # full, incremental
|
||||||
|
parent_backup_id = Column(Integer, ForeignKey("backups.id"), nullable=True)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
execution = relationship("JobExecution", back_populates="backups")
|
||||||
|
parent_backup = relationship("Backup", remote_side=[id])
|
||||||
|
|
||||||
|
class Setting(Base):
|
||||||
|
__tablename__ = "settings"
|
||||||
|
|
||||||
|
key = Column(String, primary_key=True)
|
||||||
|
value = Column(Text, nullable=True)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
Reference in New Issue
Block a user