Skip to content
Open
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
8 changes: 8 additions & 0 deletions .github/workflows/backend-Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ jobs:
env:
DATABASE_URL: postgresql+psycopg://cms:cmspass@localhost:5432/cmstest
ALEMBIC_UPGRADE_HEAD_ON_START: false
STAGING_WAREHOUSE_ID: 11111111-1111-1111-1111-111111111111
STAGING_BASE_PATH: staging
QUARANTINE_WAREHOUSE_ID: 11111111-1111-1111-1111-111111111111
QUARANTINE_BASE_PATH: quarantine

steps:
- name: Retrieve source code
Expand Down Expand Up @@ -78,6 +82,8 @@ jobs:
--network host \
-e DATABASE_URL=$DATABASE_URL \
-e JWT_SECRET=DH8kSxcflUVfNRdkEiJJCn2dOOKI3qfw \
-e STAGING_WAREHOUSE_ID=11111111-1111-1111-1111-111111111111 \
-e QUARANTINE_WAREHOUSE_ID=11111111-1111-1111-1111-111111111111 \
-p 8000:80 \
cms-backend-api:test
# wait for container to be ready
Expand Down Expand Up @@ -110,6 +116,8 @@ jobs:
docker run -d --name cms-shuttle-test \
--network host \
-e DATABASE_URL=$DATABASE_URL \
-e QUARANTINE_WAREHOUSE_ID=11111111-1111-1111-1111-111111111111 \
-e STAGING_WAREHOUSE_ID=11111111-1111-1111-1111-111111111111 \
cms-backend-shuttle:test cms-shuttle --help

- name: Run tests
Expand Down
97 changes: 50 additions & 47 deletions backend/src/cms_backend/api/routes/books.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
from typing import Annotated
from typing import Annotated, Literal
from uuid import UUID

from fastapi import APIRouter, Depends, Path, Query
from sqlalchemy.orm import Session as OrmSession

from cms_backend.api.routes.dependencies import require_permission
from cms_backend.api.routes.fields import LimitFieldMax200, NotEmptyString, SkipField
from cms_backend.api.routes.models import ListResponse, calculate_pagination_metadata
from cms_backend.db import gen_dbsession
from cms_backend.db.books import get_book as db_get_book
from cms_backend.db.book import create_book_full_schema
from cms_backend.db.book import delete_book as db_delete_book
from cms_backend.db.book import get_book as db_get_book
from cms_backend.db.book import move_book as db_move_book
from cms_backend.db.book import recover_book as db_recover_book
from cms_backend.db.books import get_books as db_get_books
from cms_backend.db.books import get_zim_urls as db_get_zim_urls
from cms_backend.schemas import BaseModel
from cms_backend.schemas.models import ZimUrlsSchema
from cms_backend.schemas.orms import (
BookFullSchema,
BookLightSchema,
BookLocationSchema,
)

router = APIRouter(prefix="/books", tags=["books"])


class BookMoveSchema(BaseModel):
destination: Literal["prod", "staging"]


class BooksGetSchema(BaseModel):
skip: SkipField = 0
limit: LimitFieldMax200 = 20
Expand Down Expand Up @@ -76,49 +84,44 @@ def get_book(
session: Annotated[OrmSession, Depends(gen_dbsession)],
) -> BookFullSchema:
"""Get a book by ID"""
return create_book_full_schema(db_get_book(session=session, book_id=book_id))


@router.delete(
"/{book_id}",
dependencies=[Depends(require_permission(namespace="book", name="delete"))],
)
def delete_book(
book_id: Annotated[UUID, Path()],
session: Annotated[OrmSession, Depends(gen_dbsession)],
*,
force_delete: Annotated[bool, Query()] = False,
) -> BookFullSchema:
return create_book_full_schema(
db_delete_book(session, book_id=book_id, force_delete=force_delete)
)


db_book = db_get_book(session=session, book_id=book_id)

# Separate current and target locations
current_locations = [
BookLocationSchema(
warehouse_name=location.warehouse.name,
path=str(location.path),
filename=location.filename,
status=location.status,
)
for location in db_book.locations
if location.status == "current"
]

target_locations = [
BookLocationSchema(
warehouse_name=location.warehouse.name,
path=str(location.path),
filename=location.filename,
status=location.status,
)
for location in db_book.locations
if location.status == "target"
]

return BookFullSchema(
id=db_book.id,
title_id=db_book.title_id,
needs_processing=db_book.needs_processing,
has_error=db_book.has_error,
needs_file_operation=db_book.needs_file_operation,
location_kind=db_book.location_kind,
created_at=db_book.created_at,
name=db_book.name,
date=db_book.date,
flavour=db_book.flavour,
article_count=db_book.article_count,
media_count=db_book.media_count,
size=db_book.size,
zimcheck_result_url=db_book.zimcheck_result_url,
zim_metadata=db_book.zim_metadata,
events=db_book.events,
current_locations=current_locations,
target_locations=target_locations,
@router.post(
"/{book_id}/recover",
dependencies=[Depends(require_permission(namespace="book", name="update"))],
)
def recover_book(
book_id: Annotated[UUID, Path()],
session: Annotated[OrmSession, Depends(gen_dbsession)],
) -> BookFullSchema:
return create_book_full_schema(db_recover_book(session, book_id=book_id))


@router.post(
"/{book_id}/move",
dependencies=[Depends(require_permission(namespace="book", name="update"))],
)
def move_book(
book_id: Annotated[UUID, Path()],
session: Annotated[OrmSession, Depends(gen_dbsession)],
request: BookMoveSchema,
) -> BookFullSchema:
return create_book_full_schema(
db_move_book(session, book_id=book_id, destination=request.destination)
)
20 changes: 20 additions & 0 deletions backend/src/cms_backend/context.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import dataclasses
import os
from dataclasses import field
from datetime import timedelta
from pathlib import Path
from typing import Any, TypeVar
from uuid import UUID

from humanfriendly import parse_timespan

T = TypeVar("T")

Expand Down Expand Up @@ -37,3 +42,18 @@ class Context:
alembic_upgrade_head_on_start: bool = parse_bool(
get_mandatory_env("ALEMBIC_UPGRADE_HEAD_ON_START")
)

# delay before old books are deleted
old_book_deletion_delay: timedelta = timedelta(
seconds=parse_timespan(os.getenv("OLD_BOOK_DELETION_DELAY", default="1d"))
)
staging_warehouse_id: UUID = field(
default=UUID(get_mandatory_env("STAGING_WAREHOUSE_ID"))
)
staging_base_path: Path = field(default=Path(os.getenv("STAGING_BASE_PATH", "")))
quarantine_warehouse_id: UUID = field(
default=UUID(get_mandatory_env("QUARANTINE_WAREHOUSE_ID"))
)
quarantine_base_path: Path = field(
default=Path(os.getenv("QUARANTINE_BASE_PATH", ""))
)
Loading