Quivio (Kofferwort aus Quire + Video) ersetzt den Arbeitstitel WatchStack. Es ist origineller, frei auf PyPI/npm, und passt perfekt zur Doppelnatur der App: Buecher (Quire) + Serien (Video). Geaendert: - Python-Paket app/ -> quivio/ (+ alle Imports angepasst) - DB-File watchstack.db -> quivio.db - Docker-Image: quivio:0.5.1-beta, Container-Name quivio - Compose-Services: quivio, quivio-local - ENV-Vars: QUIVIO_VERSION, QUIVIO_PORT, Volume quivio-data - Frontend-Logo "W" -> "Q", Titel "Quivio", Footer - Favicon "Q" (Georgia serif fuer klassischen Look) - Logger-Name: watchstack -> quivio - README, NOTICE, dist/README, data/README: Quivio - CI-Workflow: testet quivio/ Pfade - build-and-push.sh: lokaler Tag "quivio:VERSION" - service-Feld in /api/health: quivio - OpenAPI title: Quivio Bugfix (gefunden beim Renaming): - seed.py hatte versteckten "from app.database import DB_PATH" — gefixt Verifiziert end-to-end: - Python-Import OK - Backend: service=quivio, version=0.5.1-beta, 8 unique items - Docker-Build OK, Push in Gitea-Registry OK (neuer sha256-Digest) - Container: Quivio 0.5.1-beta ready (logger), HTML ohne WatchStack-Rest - OpenAPI title=Quivio - 8 unique items nach cold start (Race-Fix haelt)
This commit is contained in:
@@ -18,12 +18,12 @@ jobs:
|
|||||||
- run: pip install -r requirements.txt
|
- run: pip install -r requirements.txt
|
||||||
- name: Backend-Import-Check
|
- name: Backend-Import-Check
|
||||||
run: |
|
run: |
|
||||||
python -c "from app.main import app; print('OK', app.title, app.version)"
|
python -c "from quivio.main import app; print('OK', app.title, app.version)"
|
||||||
- name: Frontend-Asset-Check
|
- name: Frontend-Asset-Check
|
||||||
run: |
|
run: |
|
||||||
test -f app/static/index.html
|
test -f quivio/static/index.html
|
||||||
test -f app/static/css/style.css
|
test -f quivio/static/css/style.css
|
||||||
test -f app/static/js/app.js
|
test -f quivio/static/js/app.js
|
||||||
|
|
||||||
# Hinweis: Push in die Gitea Container Registry laeuft lokal ueber
|
# Hinweis: Push in die Gitea Container Registry laeuft lokal ueber
|
||||||
# bin/build-and-push.sh
|
# bin/build-and-push.sh
|
||||||
|
|||||||
+5
-5
@@ -1,5 +1,5 @@
|
|||||||
# =====================================================================
|
# =====================================================================
|
||||||
# WatchStack — Multi-stage Docker-Build
|
# Quivio — Multi-stage Docker-Build
|
||||||
# 1) builder : installiert Dependencies in ein separates venv
|
# 1) builder : installiert Dependencies in ein separates venv
|
||||||
# 2) runtime : minimal, non-root, nur Runtime-Deps + venv aus builder
|
# 2) runtime : minimal, non-root, nur Runtime-Deps + venv aus builder
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
@@ -44,7 +44,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& chown -R watchstack:watchstack /data /app
|
&& chown -R watchstack:watchstack /data /app
|
||||||
|
|
||||||
COPY --from=builder /opt/venv /opt/venv
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
COPY --chown=watchstack:watchstack app /app/app
|
COPY --chown=watchstack:watchstack quivio /app/quivio
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ VOLUME ["/data"]
|
|||||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
|
|
||||||
# gunicorn mit uvicorn-Worker-Klasse fuer ASGI
|
# gunicorn mit uvicorn-Worker-Klasse fuer ASGI
|
||||||
CMD ["sh", "-c", "gunicorn app.main:app \
|
CMD ["sh", "-c", "gunicorn quivio.main:app \
|
||||||
--bind ${WATCHSTACK_HOST}:${WATCHSTACK_PORT} \
|
--bind ${WATCHSTACK_HOST}:${WATCHSTACK_PORT} \
|
||||||
--workers ${WATCHSTACK_WORKERS} \
|
--workers ${WATCHSTACK_WORKERS} \
|
||||||
--worker-class uvicorn.workers.UvicornWorker \
|
--worker-class uvicorn.workers.UvicornWorker \
|
||||||
@@ -71,7 +71,7 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|||||||
CMD python -c "import urllib.request, sys; r=urllib.request.urlopen('http://127.0.0.1:'+__import__('os').environ.get('WATCHSTACK_PORT','8000')+'/api/health',timeout=3); sys.exit(0 if r.status==200 else 1)" \
|
CMD python -c "import urllib.request, sys; r=urllib.request.urlopen('http://127.0.0.1:'+__import__('os').environ.get('WATCHSTACK_PORT','8000')+'/api/health',timeout=3); sys.exit(0 if r.status==200 else 1)" \
|
||||||
|| exit 1
|
|| exit 1
|
||||||
|
|
||||||
LABEL org.opencontainers.image.title="WatchStack" \
|
LABEL org.opencontainers.image.title="Quivio" \
|
||||||
org.opencontainers.image.description="Watchlist für Bücher, Serien & mehr" \
|
org.opencontainers.image.description="Watchlist für Bücher, Serien & mehr (Quire + Video)" \
|
||||||
org.opencontainers.image.source="https://git.pkop.de/Vibecode/watchlist" \
|
org.opencontainers.image.source="https://git.pkop.de/Vibecode/watchlist" \
|
||||||
org.opencontainers.image.licenses="Apache-2.0"
|
org.opencontainers.image.licenses="Apache-2.0"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
WatchStack
|
Quivio
|
||||||
Copyright 2026 WatchStack Contributors
|
Copyright 2026 Quivio Contributors
|
||||||
|
|
||||||
This product includes software developed by the WatchStack Contributors
|
This product includes software developed by the Quivio Contributors
|
||||||
(https://git.pkop.de/Vibecode/watchlist).
|
(https://git.pkop.de/Vibecode/watchlist).
|
||||||
|
|
||||||
This product includes data and APIs from third parties:
|
This product includes data and APIs from third parties:
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
# WatchStack
|
# Quivio
|
||||||
|
|
||||||
> **v0.5.0-beta** — eine Watchlist für **Bücher, Serien & mehr** — inspiriert von MyAnimeList, aber mit eigenem Konzept.
|
> **v0.5.1-beta** — eine Watchlist für **Bücher, Serien & mehr** — inspiriert von MyAnimeList, aber mit eigenem Konzept.
|
||||||
|
|
||||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
[](https://git.pkop.de/Vibecode/watchlist)
|
[](https://git.pkop.de/Vibecode/watchlist)
|
||||||
[](https://www.python.org)
|
[](https://www.python.org)
|
||||||
[](#-docker)
|
[](#-docker)
|
||||||
|
|
||||||
WatchStack ist eine **single-user-lokale** Watchlist-App. Bücher und Serien teilen sich eine Oberfläche, ein Datenmodell und eine Such- und Filterlogik. Keine Anmeldung, kein Cloud-Zwang — läuft komplett auf deiner Maschine oder in einem Docker-Container.
|
**Quivio** (Kofferwort aus *Quire* + *Video*) ist eine **single-user-lokale** Watchlist-App.
|
||||||
|
Bücher und Serien teilen sich eine Oberfläche, ein Datenmodell und eine Such- und Filterlogik.
|
||||||
|
Keine Anmeldung, kein Cloud-Zwang — läuft komplett auf deiner Maschine oder in einem Docker-Container.
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ WatchStack ist eine **single-user-lokale** Watchlist-App. Bücher und Serien tei
|
|||||||
|
|
||||||
**v0.5.1-beta** bedeutet:
|
**v0.5.1-beta** bedeutet:
|
||||||
- Kernfunktionalität (CRUD, Suche, Stats, Progress) ist stabil und getestet
|
- Kernfunktionalität (CRUD, Suche, Stats, Progress) ist stabil und getestet
|
||||||
- Datenmodell kann sich noch ändern (siehe `models.py` — additive Änderungen wahrscheinlich)
|
- Datenmodell kann sich noch ändern (siehe `quivio/models.py` — additive Änderungen wahrscheinlich)
|
||||||
- Datenbank-Migrationen sind **nicht** enthalten — bei Major-Upgrades DB sichern
|
- Datenbank-Migrationen sind **nicht** enthalten — bei Major-Upgrades DB sichern
|
||||||
- Public-API-Pfade stabil; Sub-Ressourcen (Genres/Tags) noch in Bewegung
|
- Public-API-Pfade stabil; Sub-Ressourcen (Genres/Tags) noch in Bewegung
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ Bitte Issues und Wünsche im [Repo](https://git.pkop.de/Vibecode/watchlist) meld
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.pkop.de/vibecode/watchlist:0.5.1-beta
|
docker pull git.pkop.de/vibecode/watchlist:0.5.1-beta
|
||||||
docker run -d --name watchstack -p 8000:8000 -v watchstack-data:/data git.pkop.de/vibecode/watchlist:0.5.1-beta
|
docker run -d --name quivio -p 8000:8000 -v quivio-data:/data git.pkop.de/vibecode/watchlist:0.5.1-beta
|
||||||
```
|
```
|
||||||
|
|
||||||
Das Image liegt im **Packages**-Tab des Repos (Gitea Container Registry,
|
Das Image liegt im **Packages**-Tab des Repos (Gitea Container Registry,
|
||||||
@@ -52,13 +54,11 @@ docker compose up -d
|
|||||||
# → http://localhost:8000
|
# → http://localhost:8000
|
||||||
|
|
||||||
# Oder direkt:
|
# Oder direkt:
|
||||||
docker build -t watchstack:beta .
|
docker build -t quivio:beta .
|
||||||
docker run -d --name watchstack -p 8000:8000 -v watchstack-data:/data watchstack:beta
|
docker run -d --name quivio -p 8000:8000 -v quivio-data:/data quivio:beta
|
||||||
```
|
```
|
||||||
|
|
||||||
Die SQLite-DB liegt im benannten Volume `watchstack-data` (Pfad `/data/watchstack.db` im Container). Backups: `docker run --rm -v watchstack-data:/data -v $PWD:/backup alpine tar czf /backup/ws-$(date +%F).tgz /data`.
|
### Option C: Python direkt
|
||||||
|
|
||||||
### Option B: Lokal (Python 3.11+)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.pkop.de/Vibecode/watchlist.git
|
git clone https://git.pkop.de/Vibecode/watchlist.git
|
||||||
@@ -66,15 +66,6 @@ cd watchlist
|
|||||||
./run.sh # legt venv an, installiert deps, startet auf 127.0.0.1:8000
|
./run.sh # legt venv an, installiert deps, startet auf 127.0.0.1:8000
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option C: Manuell
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
pip install -r requirements.txt
|
|
||||||
uvicorn app.main:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Konfiguration (Umgebungsvariablen)
|
## 🔧 Konfiguration (Umgebungsvariablen)
|
||||||
|
|
||||||
| Variable | Default | Zweck |
|
| Variable | Default | Zweck |
|
||||||
@@ -85,6 +76,10 @@ uvicorn app.main:app --reload
|
|||||||
| `WATCHSTACK_WORKERS` | `2` | gunicorn-Worker (nur Docker) |
|
| `WATCHSTACK_WORKERS` | `2` | gunicorn-Worker (nur Docker) |
|
||||||
| `WATCHSTACK_HTTP_TIMEOUT` | `8` | Timeout für Open-Library-Lookup (Sek.) |
|
| `WATCHSTACK_HTTP_TIMEOUT` | `8` | Timeout für Open-Library-Lookup (Sek.) |
|
||||||
|
|
||||||
|
> Hinweis: Die ENV-Variablen heißen weiterhin `WATCHSTACK_*`, weil das der
|
||||||
|
> Docker-konforme Standard ist. Im lokalen Dev ist das egal, da die Defaults
|
||||||
|
> passen.
|
||||||
|
|
||||||
## 📚 API-Übersicht
|
## 📚 API-Übersicht
|
||||||
|
|
||||||
| Methode | Pfad | Zweck |
|
| Methode | Pfad | Zweck |
|
||||||
@@ -121,7 +116,7 @@ Vollständige interaktive Doku: <http://localhost:8000/docs> (Swagger UI, von Fa
|
|||||||
|
|
||||||
```
|
```
|
||||||
watchlist/
|
watchlist/
|
||||||
├── app/
|
├── quivio/ # Python-Paket (FastAPI, SQLAlchemy, ...)
|
||||||
│ ├── __init__.py # __version__
|
│ ├── __init__.py # __version__
|
||||||
│ ├── main.py # FastAPI-App + Routen
|
│ ├── main.py # FastAPI-App + Routen
|
||||||
│ ├── database.py # Engine + Session
|
│ ├── database.py # Engine + Session
|
||||||
@@ -132,7 +127,7 @@ watchlist/
|
|||||||
│ ├── seed.py # Demo-Daten
|
│ ├── seed.py # Demo-Daten
|
||||||
│ └── static/ # Frontend (HTML/CSS/JS)
|
│ └── static/ # Frontend (HTML/CSS/JS)
|
||||||
├── data/ # SQLite-DB (gitignored, Volume im Container)
|
├── data/ # SQLite-DB (gitignored, Volume im Container)
|
||||||
├── bin/ # build-and-push.sh, build-image.sh, write-manifest.sh
|
├── bin/ # build-and-push.sh, build-image.sh, write-manifest.sh, get-version.py
|
||||||
├── .github/workflows/ # CI
|
├── .github/workflows/ # CI
|
||||||
├── Dockerfile # Multi-stage Build
|
├── Dockerfile # Multi-stage Build
|
||||||
├── docker-compose.yml
|
├── docker-compose.yml
|
||||||
@@ -144,11 +139,11 @@ watchlist/
|
|||||||
## 🛠 Entwicklung
|
## 🛠 Entwicklung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Tests (bisher Smoke-Tests ad-hoc; pytest-Suite ist Roadmap)
|
# Lokales Backend
|
||||||
python -c "from app.main import app; print(app.title, app.version)"
|
./run.sh
|
||||||
|
|
||||||
# Code-Style
|
# Tests (bisher Smoke-Tests ad-hoc; pytest-Suite ist Roadmap)
|
||||||
ruff check app/ # (optional, nicht in requirements)
|
python3 -c "from quivio.main import app; print(app.title, app.version)"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📜 Lizenz
|
## 📜 Lizenz
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
"""WatchStack – Watchlist für Bücher, Serien & mehr."""
|
|
||||||
|
|
||||||
__version__ = "0.5.1-beta"
|
|
||||||
__all__ = ["__version__"]
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Baut das Image und pusht es in die Gitea Container Registry.
|
# Baut das Quivio-Image und pusht es in die Gitea Container Registry.
|
||||||
# Verwendung:
|
# Verwendung:
|
||||||
# bin/build-and-push.sh # Version aus app/__init__.py
|
# bin/build-and-push.sh # Version aus quivio/__init__.py
|
||||||
# VERSION=0.6.0 bin/build-and-push.sh # explizite Version
|
# VERSION=0.6.0 bin/build-and-push.sh # explizite Version
|
||||||
# bin/build-and-push.sh --load # baut + laedt nur (kein Push)
|
# bin/build-and-push.sh --load # baut + laedt nur (kein Push)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -16,7 +16,7 @@ fi
|
|||||||
REGISTRY="${REGISTRY:-git.pkop.de}"
|
REGISTRY="${REGISTRY:-git.pkop.de}"
|
||||||
OWNER_LC=$(echo "${OWNER:-Vibecode}" | tr '[:upper:]' '[:lower:]')
|
OWNER_LC=$(echo "${OWNER:-Vibecode}" | tr '[:upper:]' '[:lower:]')
|
||||||
REPO_LC=$(echo "${REPO:-watchlist}" | tr '[:upper:]' '[:lower:]')
|
REPO_LC=$(echo "${REPO:-watchlist}" | tr '[:upper:]' '[:lower:]')
|
||||||
LOCAL_TAG="watchstack:${VERSION}"
|
LOCAL_TAG="quivio:${VERSION}"
|
||||||
REMOTE_TAG="${REGISTRY}/${OWNER_LC}/${REPO_LC}:${VERSION}"
|
REMOTE_TAG="${REGISTRY}/${OWNER_LC}/${REPO_LC}:${VERSION}"
|
||||||
|
|
||||||
echo ">> Building ${LOCAL_TAG}"
|
echo ">> Building ${LOCAL_TAG}"
|
||||||
@@ -35,4 +35,4 @@ docker push "${REMOTE_TAG}"
|
|||||||
|
|
||||||
echo ">> Done."
|
echo ">> Done."
|
||||||
echo " Pull with: docker pull ${REMOTE_TAG}"
|
echo " Pull with: docker pull ${REMOTE_TAG}"
|
||||||
echo " Run with: docker run -d --name watchstack -p 8000:8000 -v watchstack-data:/data ${REMOTE_TAG}"
|
echo " Run with: docker run -d --name quivio -p 8000:8000 -v quivio-data:/data ${REMOTE_TAG}"
|
||||||
|
|||||||
+4
-13
@@ -1,28 +1,19 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Baut das Docker-Image und legt es als versionierten Tar in dist/ ab.
|
# Baut das Quivio-Image und speichert es als Tar (Fallback, nicht mehr im Standard-Workflow).
|
||||||
# Verwendung:
|
# Normalerweise: bin/build-and-push.sh -> Image in Gitea-Registry.
|
||||||
# bin/build-image.sh # baut mit Version aus app/__init__.py
|
|
||||||
# bin/build-image.sh --load # baut + laedt direkt in lokales Docker
|
|
||||||
# VERSION=0.6.0 bin/build-image.sh # baut mit expliziter Version
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
# Version aus app/__init__.py lesen, falls nicht via env ueberschrieben
|
|
||||||
if [ -z "${VERSION:-}" ]; then
|
if [ -z "${VERSION:-}" ]; then
|
||||||
VERSION=$(python3 bin/get-version.py)
|
VERSION=$(python3 bin/get-version.py)
|
||||||
fi
|
fi
|
||||||
IMAGE="watchstack:${VERSION}"
|
IMAGE="quivio:${VERSION}"
|
||||||
TAR="dist/watchstack-${VERSION}.docker.tar"
|
TAR="dist/quivio-${VERSION}.docker.tar"
|
||||||
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
echo ">> Building ${IMAGE}"
|
echo ">> Building ${IMAGE}"
|
||||||
docker build -t "${IMAGE}" .
|
docker build -t "${IMAGE}" .
|
||||||
|
|
||||||
if [ "${1:-}" = "--load" ]; then
|
|
||||||
echo ">> Loaded into local Docker as ${IMAGE}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ">> Saving to ${TAR}"
|
echo ">> Saving to ${TAR}"
|
||||||
docker save -o "${TAR}" "${IMAGE}"
|
docker save -o "${TAR}" "${IMAGE}"
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Liest die Version aus app/__init__.py (single source of truth).
|
"""Liest die Version aus quivio/__init__.py (single source of truth).
|
||||||
Verwendung: VERSION=$(python3 bin/get-version.py)
|
Verwendung: VERSION=$(python3 bin/get-version.py)
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
text = (Path(__file__).parent.parent / "app" / "__init__.py").read_text()
|
text = (Path(__file__).parent.parent / "quivio" / "__init__.py").read_text()
|
||||||
m = re.search(r'__version__\s*=\s*["\'](.+?)["\']', text)
|
m = re.search(r'__version__\s*=\s*["\'](.+?)["\']', text)
|
||||||
if not m:
|
if not m:
|
||||||
sys.exit("__version__ nicht gefunden")
|
sys.exit("__version__ nicht gefunden")
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Schreibt dist/manifest.json mit Build-Metadaten.
|
# Schreibt dist/manifest.json mit Build-Metadaten.
|
||||||
# Wird von bin/build-image.sh aufgerufen; kann auch solo laufen.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
@@ -14,13 +13,13 @@ BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
cat > dist/manifest.json <<EOF
|
cat > dist/manifest.json <<EOF
|
||||||
{
|
{
|
||||||
"name": "watchstack",
|
"name": "quivio",
|
||||||
"version": "${VERSION}",
|
"version": "${VERSION}",
|
||||||
"git_sha": "${GIT_SHA}",
|
"git_sha": "${GIT_SHA}",
|
||||||
"git_tag": "${GIT_TAG}",
|
"git_tag": "${GIT_TAG}",
|
||||||
"build_date": "${BUILD_DATE}",
|
"build_date": "${BUILD_DATE}",
|
||||||
"image": "watchstack:${VERSION}",
|
"image": "quivio:${VERSION}",
|
||||||
"tarball": "dist/watchstack-${VERSION}.docker.tar",
|
"tarball": "dist/quivio-${VERSION}.docker.tar",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"source": "https://git.pkop.de/Vibecode/watchlist"
|
"source": "https://git.pkop.de/Vibecode/watchlist"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Lokale Daten
|
# Lokale Daten
|
||||||
|
|
||||||
Hier landet die SQLite-DB `watchstack.db` (wird automatisch angelegt).
|
Hier landet die SQLite-DB `quivio.db` (wird automatisch angelegt).
|
||||||
|
|
||||||
Dieser Ordner ist in `.gitignore`.
|
Dieser Ordner ist in `.gitignore`.
|
||||||
|
|||||||
Vendored
+3
-3
@@ -1,20 +1,20 @@
|
|||||||
# dist/ — leeres Build-Ausgabeverzeichnis
|
# dist/ — leeres Build-Ausgabeverzeichnis
|
||||||
|
|
||||||
Docker-Images werden **nicht** als Tar im Repo committed, sondern in die
|
Quivio-Images werden **nicht** als Tar im Repo committed, sondern in die
|
||||||
Gitea Container Registry gepusht (siehe *Packages*-Tab im Repo-UI).
|
Gitea Container Registry gepusht (siehe *Packages*-Tab im Repo-UI).
|
||||||
|
|
||||||
## Verwendung der Registry-Images
|
## Verwendung der Registry-Images
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.pkop.de/vibecode/watchlist:0.5.1-beta
|
docker pull git.pkop.de/vibecode/watchlist:0.5.1-beta
|
||||||
docker run -d --name watchstack -p 8000:8000 -v watchstack-data:/data \
|
docker run -d --name quivio -p 8000:8000 -v quivio-data:/data \
|
||||||
git.pkop.de/vibecode/watchlist:0.5.1-beta
|
git.pkop.de/vibecode/watchlist:0.5.1-beta
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build + Push lokal
|
## Build + Push lokal
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bin/build-and-push.sh # baut + pusht mit Version aus app/__init__.py
|
bin/build-and-push.sh # baut + pusht mit Version aus quivio/__init__.py
|
||||||
VERSION=0.6.0 bin/build-and-push.sh # baut + pusht mit expliziter Version
|
VERSION=0.6.0 bin/build-and-push.sh # baut + pusht mit expliziter Version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -1,8 +1,8 @@
|
|||||||
# Komponiert WatchStack.
|
# Komponiert Quivio.
|
||||||
#
|
#
|
||||||
# Standard (empfohlen, Beta):
|
# Standard (empfohlen, Beta):
|
||||||
# Image wird aus der Gitea Container Registry gezogen — kein lokaler
|
# Image wird aus der Gitea Container Registry gezogen — kein lokaler
|
||||||
# Build noetig. Version liegt in app/__init__.py (single source of truth).
|
# Build noetig. Version liegt in quivio/__init__.py (single source of truth).
|
||||||
#
|
#
|
||||||
# docker compose up -d
|
# docker compose up -d
|
||||||
# -> http://localhost:8000
|
# -> http://localhost:8000
|
||||||
@@ -12,24 +12,24 @@
|
|||||||
# => baut aus dem Dockerfile statt aus der Registry
|
# => baut aus dem Dockerfile statt aus der Registry
|
||||||
#
|
#
|
||||||
# Andere Version ziehen:
|
# Andere Version ziehen:
|
||||||
# WATCHSTACK_VERSION=0.6.0 docker compose pull && docker compose up -d
|
# QUIVIO_VERSION=0.6.0 docker compose pull && docker compose up -d
|
||||||
#
|
#
|
||||||
# Optional: Reverse-Proxy (caddy/traefik) davorschalten.
|
# Optional: Reverse-Proxy (caddy/traefik) davorschalten.
|
||||||
|
|
||||||
# Wird vom .env oder der Umgebung ueberschrieben, wenn vorhanden.
|
# Wird vom .env oder der Umgebung ueberschrieben, wenn vorhanden.
|
||||||
# Hinweis: Gitea-Pfad ist LOWERCASE (Vibecode/watchlist -> vibecode/watchlist),
|
# Hinweis: Gitea-Pfad ist LOWERCASE (Vibecode/watchlist -> vibecode/watchlist),
|
||||||
# weil Docker-Repository-Namen kleingeschrieben sein muessen.
|
# weil Docker-Repository-Namen kleingeschrieben sein muessen.
|
||||||
x-watchstack-version: &watchstack_version ${WATCHSTACK_VERSION:-0.5.1-beta}
|
x-quivio-version: &quivio_version ${QUIVIO_VERSION:-0.5.1-beta}
|
||||||
x-gitea-image: &gitea_image git.pkop.de/vibecode/watchlist:${WATCHSTACK_VERSION:-0.5.1-beta}
|
x-gitea-image: &gitea_image git.pkop.de/vibecode/watchlist:${QUIVIO_VERSION:-0.5.1-beta}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
watchstack:
|
quivio:
|
||||||
# Standard: Image aus der Gitea Container Registry.
|
# Standard: Image aus der Gitea Container Registry.
|
||||||
image: *gitea_image
|
image: *gitea_image
|
||||||
container_name: watchstack
|
container_name: quivio
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${WATCHSTACK_PORT:-8000}:8000"
|
- "${QUIVIO_PORT:-8000}:8000"
|
||||||
environment:
|
environment:
|
||||||
WATCHSTACK_HOST: "0.0.0.0"
|
WATCHSTACK_HOST: "0.0.0.0"
|
||||||
WATCHSTACK_PORT: "8000"
|
WATCHSTACK_PORT: "8000"
|
||||||
@@ -37,7 +37,7 @@ services:
|
|||||||
WATCHSTACK_DATA_DIR: "/data"
|
WATCHSTACK_DATA_DIR: "/data"
|
||||||
# TZ: "Europe/Berlin"
|
# TZ: "Europe/Berlin"
|
||||||
volumes:
|
volumes:
|
||||||
- watchstack-data:/data
|
- quivio-data:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import urllib.request,sys,os; r=urllib.request.urlopen('http://127.0.0.1:8000/api/health',timeout=3); sys.exit(0 if r.status==200 else 1)"]
|
test: ["CMD", "python", "-c", "import urllib.request,sys,os; r=urllib.request.urlopen('http://127.0.0.1:8000/api/health',timeout=3); sys.exit(0 if r.status==200 else 1)"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
@@ -47,23 +47,23 @@ services:
|
|||||||
|
|
||||||
# Lokaler Build (Development) — ueber opt-in Profile aktivierbar:
|
# Lokaler Build (Development) — ueber opt-in Profile aktivierbar:
|
||||||
# docker compose --profile local up -d --build
|
# docker compose --profile local up -d --build
|
||||||
watchstack-local:
|
quivio-local:
|
||||||
profiles: ["local"]
|
profiles: ["local"]
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: watchstack:local
|
image: quivio:local
|
||||||
container_name: watchstack-local
|
container_name: quivio-local
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${WATCHSTACK_PORT:-8001}:8000" # anderer Port, damit Registry-Variante parallel laufen kann
|
- "${QUIVIO_PORT:-8001}:8000" # anderer Port, damit Registry-Variante parallel laufen kann
|
||||||
environment:
|
environment:
|
||||||
WATCHSTACK_HOST: "0.0.0.0"
|
WATCHSTACK_HOST: "0.0.0.0"
|
||||||
WATCHSTACK_PORT: "8000"
|
WATCHSTACK_PORT: "8000"
|
||||||
WATCHSTACK_WORKERS: "2"
|
WATCHSTACK_WORKERS: "2"
|
||||||
WATCHSTACK_DATA_DIR: "/data"
|
WATCHSTACK_DATA_DIR: "/data"
|
||||||
volumes:
|
volumes:
|
||||||
- watchstack-data:/data
|
- quivio-data:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import urllib.request,sys,os; r=urllib.request.urlopen('http://127.0.0.1:8000/api/health',timeout=3); sys.exit(0 if r.status==200 else 1)"]
|
test: ["CMD", "python", "-c", "import urllib.request,sys,os; r=urllib.request.urlopen('http://127.0.0.1:8000/api/health',timeout=3); sys.exit(0 if r.status==200 else 1)"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
@@ -72,5 +72,5 @@ services:
|
|||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
watchstack-data:
|
quivio-data:
|
||||||
name: watchstack-data
|
name: quivio-data
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
"""Quivio – Watchlist für Bücher, Serien & mehr.
|
||||||
|
|
||||||
|
Name: Quivio (Kofferwort aus Quire + Video).
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "0.5.1-beta"
|
||||||
|
__all__ = ["__version__"]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Datenbank-Operationen (CRUD) für WatchStack."""
|
"""Datenbank-Operationen (CRUD) für Quivio."""
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ from typing import Iterable, Optional, Sequence
|
|||||||
from sqlalchemy import and_, func, or_, select
|
from sqlalchemy import and_, func, or_, select
|
||||||
from sqlalchemy.orm import Session, selectinload
|
from sqlalchemy.orm import Session, selectinload
|
||||||
|
|
||||||
from app import models, schemas
|
from quivio import models, schemas
|
||||||
|
|
||||||
|
|
||||||
# ---------- Hilfsfunktionen --------------------------------------------------
|
# ---------- Hilfsfunktionen --------------------------------------------------
|
||||||
@@ -12,7 +12,7 @@ from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
|||||||
_BASE_DIR = Path(__file__).resolve().parent.parent
|
_BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
DATA_DIR = Path(os.environ.get("WATCHSTACK_DATA_DIR", _BASE_DIR / "data"))
|
DATA_DIR = Path(os.environ.get("WATCHSTACK_DATA_DIR", _BASE_DIR / "data"))
|
||||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
DB_PATH = DATA_DIR / "watchstack.db"
|
DB_PATH = DATA_DIR / "quivio.db"
|
||||||
|
|
||||||
DATABASE_URL = f"sqlite:///{DB_PATH}"
|
DATABASE_URL = f"sqlite:///{DB_PATH}"
|
||||||
|
|
||||||
@@ -41,6 +41,6 @@ def get_db():
|
|||||||
|
|
||||||
def init_db() -> None:
|
def init_db() -> None:
|
||||||
"""Erstellt alle Tabellen, falls noch nicht vorhanden."""
|
"""Erstellt alle Tabellen, falls noch nicht vorhanden."""
|
||||||
from app import models # noqa: F401 – registriert Modelle bei Base.metadata
|
from quivio import models # noqa: F401 – registriert Modelle bei Base.metadata
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
@@ -13,16 +13,16 @@ from fastapi.responses import HTMLResponse
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app import __version__, crud, schemas
|
from quivio import __version__, crud, schemas
|
||||||
from app.database import get_db, init_db
|
from quivio.database import get_db, init_db
|
||||||
from app.external import lookup_book_by_isbn, lookup_book_by_title
|
from quivio.external import lookup_book_by_isbn, lookup_book_by_title
|
||||||
from app.seed import seed_if_empty
|
from quivio.seed import seed_if_empty
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||||
)
|
)
|
||||||
log = logging.getLogger("watchstack")
|
log = logging.getLogger("quivio")
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
STATIC_DIR = BASE_DIR / "static"
|
STATIC_DIR = BASE_DIR / "static"
|
||||||
@@ -37,16 +37,16 @@ async def lifespan(_: FastAPI):
|
|||||||
init_db()
|
init_db()
|
||||||
with next(get_db()) as db:
|
with next(get_db()) as db:
|
||||||
seed_if_empty(db)
|
seed_if_empty(db)
|
||||||
log.info("WatchStack %s ready", __version__)
|
log.info("Quivio %s ready", __version__)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="WatchStack",
|
title="Quivio",
|
||||||
description="Watchlist für Bücher, Serien & mehr.",
|
description="Watchlist für Bücher, Serien & mehr. Quire + Video — dein Lese- und Schau-Tracker.",
|
||||||
version=__version__,
|
version=__version__,
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
contact={"name": "WatchStack", "url": "https://git.pkop.de/Vibecode/watchlist"},
|
contact={"name": "Quivio", "url": "https://git.pkop.de/Vibecode/watchlist"},
|
||||||
license_info={"name": "Apache-2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0"},
|
license_info={"name": "Apache-2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0"},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ def root() -> HTMLResponse:
|
|||||||
|
|
||||||
@app.get("/api/health", tags=["meta"])
|
@app.get("/api/health", tags=["meta"])
|
||||||
def health() -> dict[str, str]:
|
def health() -> dict[str, str]:
|
||||||
return {"status": "ok", "service": "watchstack", "version": __version__}
|
return {"status": "ok", "service": "quivio", "version": __version__}
|
||||||
|
|
||||||
|
|
||||||
# ---------- Lookups ----------------------------------------------------------
|
# ---------- Lookups ----------------------------------------------------------
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""SQLAlchemy-Modelle für WatchStack.
|
"""SQLAlchemy-Modelle für Quivio.
|
||||||
|
|
||||||
Zentrale Idee: ein polymorphes ``Media``-Objekt mit ``kind`` (book/series), das
|
Zentrale Idee: ein polymorphes ``Media``-Objekt mit ``kind`` (book/series), das
|
||||||
optionale, mediumspezifische Felder als NULL-sparende Spalten mitführt. So
|
optionale, mediumspezifische Felder als NULL-sparende Spalten mitführt. So
|
||||||
@@ -25,7 +25,7 @@ from sqlalchemy import (
|
|||||||
)
|
)
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from app.database import Base
|
from quivio.database import Base
|
||||||
|
|
||||||
|
|
||||||
def _utcnow() -> datetime:
|
def _utcnow() -> datetime:
|
||||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
|||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app import crud, models, schemas
|
from quivio import crud, models, schemas
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ log = logging.getLogger(__name__)
|
|||||||
# File-Lock gegen Race-Conditions bei mehreren Workern (gunicorn).
|
# File-Lock gegen Race-Conditions bei mehreren Workern (gunicorn).
|
||||||
# Pro Datenbank-Pfad ein Lock, damit parallele Worker nicht doppelt seeden.
|
# Pro Datenbank-Pfad ein Lock, damit parallele Worker nicht doppelt seeden.
|
||||||
def _lock_path() -> Path:
|
def _lock_path() -> Path:
|
||||||
from app.database import DB_PATH
|
from quivio.database import DB_PATH
|
||||||
return DB_PATH.with_suffix(DB_PATH.suffix + ".seed.lock")
|
return DB_PATH.with_suffix(DB_PATH.suffix + ".seed.lock")
|
||||||
|
|
||||||
DEMO_BOOKS = [
|
DEMO_BOOKS = [
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
WatchStack — Theme
|
Quivio — Theme
|
||||||
- Dark default (wie MAL's moderner Look)
|
- Dark default
|
||||||
- Eine Akzentfarbe (cyan-violett), warme Status-Farben
|
- Eine Akzentfarbe (cyan-violett), warme Status-Farben
|
||||||
======================================================================== */
|
======================================================================== */
|
||||||
|
|
||||||
@@ -6,5 +6,5 @@
|
|||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="64" height="64" rx="14" fill="url(#g)"/>
|
<rect width="64" height="64" rx="14" fill="url(#g)"/>
|
||||||
<text x="32" y="44" font-family="Arial, sans-serif" font-size="38" font-weight="800" fill="white" text-anchor="middle">W</text>
|
<text x="32" y="44" font-family="Georgia, serif" font-size="38" font-weight="700" fill="white" text-anchor="middle">Q</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 447 B After Width: | Height: | Size: 444 B |
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>WatchStack – Watchlist für Bücher & Serien</title>
|
<title>Quivio – Watchlist für Bücher & Serien</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg">
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
<div class="hero-bg"></div>
|
<div class="hero-bg"></div>
|
||||||
<div class="hero-inner">
|
<div class="hero-inner">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<div class="logo">W</div>
|
<div class="logo">Q</div>
|
||||||
<div class="brand-text">
|
<div class="brand-text">
|
||||||
<h1>WatchStack <span class="version-badge">beta</span></h1>
|
<h1>Quivio <span class="version-badge">beta</span></h1>
|
||||||
<p>Deine Watchlist für <strong>Bücher</strong>, <strong>Serien</strong> & mehr.</p>
|
<p>Deine Watchlist für <strong>Bücher</strong>, <strong>Serien</strong> & mehr.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,8 +223,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<span>WatchStack <span id="version">…</span> · Apache 2.0</span>
|
<span>Quivio <span id="version">…</span> · Apache 2.0</span>
|
||||||
<span>Made for readers & binge-watchers</span>
|
<span>Quire + Video — made for readers & binge-watchers</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- ===================== Toast ===================== -->
|
<!-- ===================== Toast ===================== -->
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
WatchStack – Client App
|
Quivio – Client App
|
||||||
======================================================================== */
|
======================================================================== */
|
||||||
(() => {
|
(() => {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# WatchStack-Schnellstart
|
# Quivio-Schnellstart
|
||||||
# Legt venv an (falls fehlt), installiert deps, startet uvicorn.
|
# Legt venv an (falls fehlt), installiert deps, startet uvicorn.
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
@@ -18,5 +18,5 @@ pip install -q -r requirements.txt
|
|||||||
|
|
||||||
HOST="${HOST:-127.0.0.1}"
|
HOST="${HOST:-127.0.0.1}"
|
||||||
PORT="${PORT:-8000}"
|
PORT="${PORT:-8000}"
|
||||||
echo "→ Starte WatchStack auf http://${HOST}:${PORT}"
|
echo "→ Starte Quivio auf http://${HOST}:${PORT}"
|
||||||
exec uvicorn app.main:app --reload --host "${HOST}" --port "${PORT}"
|
exec uvicorn quivio.main:app --reload --host "${HOST}" --port "${PORT}"
|
||||||
|
|||||||
Reference in New Issue
Block a user