Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- uses: pre-commit/action@v3.0.0
with:
extra_args: --all-files
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
repos:
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.7
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
args: [--ignore-missing-imports]
files: "^(core|config)/"
9 changes: 5 additions & 4 deletions alembic/env.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import pathlib
import sys
import asyncio

sys.path.append(str((pathlib.Path(__file__).parent.parent.parent).resolve()))

Expand All @@ -11,7 +11,6 @@

from alembic import context
from database.base import Base
from models import domain_models

config = context.config

Expand Down Expand Up @@ -55,7 +54,9 @@ def run_migrations_online() -> None:

"""
config_section = config.get_section(config.config_ini_section, {})
url = config_section.get("sqlalchemy.url") or config.get_main_option("sqlalchemy.url")
url = config_section.get("sqlalchemy.url") or config.get_main_option(
"sqlalchemy.url",
)

if url and url.startswith("postgresql+asyncpg"):
# Use async engine for async DB URLs
Expand All @@ -67,7 +68,7 @@ async def run_async_migrations():
lambda sync_conn: context.configure(
connection=sync_conn,
target_metadata=target_metadata,
)
),
)
async with connection.begin():
await connection.run_sync(context.run_migrations)
Expand Down
3 changes: 2 additions & 1 deletion alembic/versions/20250617_add_book_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
Create Date: 2025-06-17
"""

from alembic import op
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision = "20250617_add_book_status"
down_revision = "983f05a87193"
Expand Down
28 changes: 18 additions & 10 deletions alembic/versions/6ce246100fed_add_user_foreign_key_to_book_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,36 @@
Create Date: 2025-06-18 23:01:09.599133

"""
from typing import Sequence, Union

from alembic import op
from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = '6ce246100fed'
down_revision: Union[str, None] = '20250617_add_book_status'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
revision: str = "6ce246100fed"
down_revision: str | None = "20250617_add_book_status"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('books', sa.Column('user_id', sa.String(), nullable=False))
op.create_foreign_key(None, 'books', 'users', ['user_id'], ['id'], ondelete='CASCADE')
op.add_column("books", sa.Column("user_id", sa.String(), nullable=False))
op.create_foreign_key(
None,
"books",
"users",
["user_id"],
["id"],
ondelete="CASCADE",
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'books', type_='foreignkey')
op.drop_column('books', 'user_id')
op.drop_constraint(None, "books", type_="foreignkey")
op.drop_column("books", "user_id")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -1,87 +1,118 @@
"""Switch to Crockford Base32 string IDs for all models

Revision ID: 983f05a87193
Revises:
Revises:
Create Date: 2025-06-16 22:22:13.279601

"""
from typing import Sequence, Union

from alembic import op
from collections.abc import Sequence

import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

from alembic import op

# revision identifiers, used by Alembic.
revision: str = '983f05a87193'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
revision: str = "983f05a87193"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('books',
sa.Column('id', sa.String(), nullable=False),
sa.Column('title', sa.String(), nullable=True),
sa.Column('authors', postgresql.ARRAY(postgresql.JSON(astext_type=sa.Text())), nullable=True),
sa.Column('publisher', sa.String(), nullable=True),
sa.Column('publication_date', sa.String(), nullable=True),
sa.Column('isbn_10', sa.String(), nullable=True),
sa.Column('isbn_13', sa.String(), nullable=True),
sa.Column('language', sa.String(), nullable=True),
sa.Column('series_name', sa.String(), nullable=True),
sa.Column('series_index', sa.Float(), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('tags', postgresql.ARRAY(sa.String()), nullable=True),
sa.Column('identifiers', postgresql.ARRAY(postgresql.JSON(astext_type=sa.Text())), nullable=True),
sa.Column('covers', postgresql.ARRAY(postgresql.JSON(astext_type=sa.Text())), nullable=False),
sa.Column('format', sa.String(), nullable=True),
sa.Column('original_filename', sa.String(), nullable=True),
sa.Column('stored_filename', sa.String(), nullable=True),
sa.Column('file_hash', sa.String(), nullable=True),
sa.Column('file_path', sa.String(), nullable=True),
sa.Column('file_size_bytes', sa.Integer(), nullable=True),
sa.Column('uploaded_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('modified_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('file_hash')
op.create_table(
"books",
sa.Column("id", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=True),
sa.Column(
"authors",
postgresql.ARRAY(postgresql.JSON(astext_type=sa.Text())),
nullable=True,
),
sa.Column("publisher", sa.String(), nullable=True),
sa.Column("publication_date", sa.String(), nullable=True),
sa.Column("isbn_10", sa.String(), nullable=True),
sa.Column("isbn_13", sa.String(), nullable=True),
sa.Column("language", sa.String(), nullable=True),
sa.Column("series_name", sa.String(), nullable=True),
sa.Column("series_index", sa.Float(), nullable=True),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("tags", postgresql.ARRAY(sa.String()), nullable=True),
sa.Column(
"identifiers",
postgresql.ARRAY(postgresql.JSON(astext_type=sa.Text())),
nullable=True,
),
sa.Column(
"covers",
postgresql.ARRAY(postgresql.JSON(astext_type=sa.Text())),
nullable=False,
),
sa.Column("format", sa.String(), nullable=True),
sa.Column("original_filename", sa.String(), nullable=True),
sa.Column("stored_filename", sa.String(), nullable=True),
sa.Column("file_hash", sa.String(), nullable=True),
sa.Column("file_path", sa.String(), nullable=True),
sa.Column("file_size_bytes", sa.Integer(), nullable=True),
sa.Column(
"uploaded_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=True,
),
sa.Column("modified_at", sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("file_hash"),
)
op.create_index(op.f('ix_books_id'), 'books', ['id'], unique=False)
op.create_table('users',
sa.Column('id', sa.String(), nullable=False),
sa.Column('username', sa.String(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('password', sa.String(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('preferences', postgresql.JSON(astext_type=sa.Text()), nullable=False),
sa.PrimaryKeyConstraint('id')
op.create_index(op.f("ix_books_id"), "books", ["id"], unique=False)
op.create_table(
"users",
sa.Column("id", sa.String(), nullable=False),
sa.Column("username", sa.String(), nullable=False),
sa.Column("email", sa.String(), nullable=False),
sa.Column("password", sa.String(), nullable=False),
sa.Column("is_active", sa.Boolean(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.Column(
"preferences",
postgresql.JSON(astext_type=sa.Text()),
nullable=False,
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
op.create_table('storage',
sa.Column('id', sa.String(), nullable=False),
sa.Column('config', sa.JSON(), nullable=False),
sa.Column('storage_type', sa.String(), nullable=False),
sa.Column('user_id', sa.String(), nullable=False),
sa.Column('is_default', sa.Boolean(), nullable=False, comment='Indicates whether this is the primary (default) storage for the user. Only one storage entry per user can have this set to True.'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
op.create_index(op.f("ix_users_id"), "users", ["id"], unique=False)
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
op.create_table(
"storage",
sa.Column("id", sa.String(), nullable=False),
sa.Column("config", sa.JSON(), nullable=False),
sa.Column("storage_type", sa.String(), nullable=False),
sa.Column("user_id", sa.String(), nullable=False),
sa.Column(
"is_default",
sa.Boolean(),
nullable=False,
comment="Indicates whether this is the primary (default) storage for the user. Only one storage entry per user can have this set to True.",
),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f('ix_storage_id'), 'storage', ['id'], unique=False)
op.create_index(op.f("ix_storage_id"), "storage", ["id"], unique=False)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_storage_id'), table_name='storage')
op.drop_table('storage')
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_table('users')
op.drop_index(op.f('ix_books_id'), table_name='books')
op.drop_table('books')
op.drop_index(op.f("ix_storage_id"), table_name="storage")
op.drop_table("storage")
op.drop_index(op.f("ix_users_username"), table_name="users")
op.drop_index(op.f("ix_users_id"), table_name="users")
op.drop_index(op.f("ix_users_email"), table_name="users")
op.drop_table("users")
op.drop_index(op.f("ix_books_id"), table_name="books")
op.drop_table("books")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@
"""

from collections.abc import Sequence
from typing import Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "a9d8b686c874"
down_revision: Union[str, None] = "6ce246100fed"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "6ce246100fed"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
Expand Down
Loading