chore: beta release v0.5.0-beta
CI / test (push) Has been cancelled
CI / docker (push) Has been cancelled

- Bump version to 0.5.0-beta
- Add Apache-2.0 LICENSE (official text) + NOTICE
- Add Docker support: multi-stage Dockerfile + docker-compose
  * non-root user, tini PID 1, gunicorn + uvicorn workers
  * healthcheck, OCI labels, persistent /data volume
- Add GitHub Actions CI (lint/import-check + Docker smoke-test)
- Backend polish:
  * modern lifespan handler (replaces deprecated on_event)
  * explicit sort validation (400 instead of 500)
  * title min_length=1 schema validation
  * configurable data dir + http timeout via env
  * avg_rating helper extracted (DRY)
- Frontend polish:
  * beta badge in title, footer with live version
  * loading spinner on initial fetch
  * updated README, badges, config table, roadmap
This commit is contained in:
ki
2026-07-21 23:34:01 +02:00
parent 56a8d5d302
commit 0bca2eaaa2
19 changed files with 736 additions and 124 deletions
+6 -6
View File
@@ -4,6 +4,7 @@ Zentrale Idee: ein polymorphes ``Media``-Objekt mit ``kind`` (book/series), das
optionale, mediumspezifische Felder als NULL-sparende Spalten mitführt. So
müssen wir keine separaten Tabellen für Bücher vs. Serien pflegen.
"""
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
from datetime import date, datetime, timezone
@@ -21,7 +22,6 @@ from sqlalchemy import (
String,
Table,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -66,7 +66,7 @@ class Tag(Base):
# ---------- Hauptentität: Media ----------------------------------------------
# Status-Werte (an MAL angelehnt, aber für Bücher + Serien nutzbar):
# Status (an MAL angelehnt, für Bücher + Serien nutzbar):
# plan -> auf der Liste / will ich lesen/schauen
# reading -> lese ich gerade (Buch) ODER watching (Serie)
# done -> fertig (abgeschlossen)
@@ -84,7 +84,7 @@ class Media(Base):
id: Mapped[int] = mapped_column(primary_key=True)
# Allgemein
kind: Mapped[str] = mapped_column(String(16), nullable=False) # book|series
kind: Mapped[str] = mapped_column(String(16), nullable=False)
title: Mapped[str] = mapped_column(String(200), nullable=False)
original_title: Mapped[Optional[str]] = mapped_column(String(200))
description: Mapped[Optional[str]] = mapped_column(Text)
@@ -97,15 +97,15 @@ class Media(Base):
# Buch-spezifisch
author: Mapped[Optional[str]] = mapped_column(String(200))
isbn: Mapped[Optional[str]] = mapped_column(String(20))
total_volumes: Mapped[Optional[int]] = mapped_column(Integer) # Bände
total_chapters: Mapped[Optional[int]] = mapped_column(Integer) # Kapitel
total_volumes: Mapped[Optional[int]] = mapped_column(Integer)
total_chapters: Mapped[Optional[int]] = mapped_column(Integer)
total_pages: Mapped[Optional[int]] = mapped_column(Integer)
volumes_read: Mapped[Optional[int]] = mapped_column(Integer, default=0)
chapters_read: Mapped[Optional[int]] = mapped_column(Integer, default=0)
pages_read: Mapped[Optional[int]] = mapped_column(Integer, default=0)
# Serien-spezifisch
network: Mapped[Optional[str]] = mapped_column(String(120)) # Sender/Streaming
network: Mapped[Optional[str]] = mapped_column(String(120))
total_seasons: Mapped[Optional[int]] = mapped_column(Integer)
total_episodes: Mapped[Optional[int]] = mapped_column(Integer)
episodes_watched: Mapped[Optional[int]] = mapped_column(Integer, default=0)