- Python 57.6%
- JavaScript 16.5%
- HTML 15.2%
- CSS 10%
- Dockerfile 0.5%
- Other 0.2%
| app | ||
| migrate | ||
| tests | ||
| .gitignore | ||
| compose.yml | ||
| Containerfile | ||
| COPYRIGHT | ||
| dev.sh | ||
| LICENSE | ||
| LICENSE-books.alexwlchan.net | ||
| README.md | ||
| requirements.txt | ||
readlist
Lese-Tracker: außen eine schlichte öffentliche Seite mit farbigen Buchkarten,
innen eine Pflegeoberfläche hinter dem Login. Löst den früheren Jekyll-Aufbau ab
(Markdown von Hand, Cover selbst herunterladen, Farbe extern ermitteln,
_site per scp hochschieben).
Was sich geändert hat
| früher | jetzt |
|---|---|
| Markdown-Datei anlegen und benennen | Formular in der Oberfläche |
| Cover suchen, benennen, ablegen | Drag&Drop oder Bild-Adresse einfügen |
| Tint-Hex extern ermitteln und eintippen | fünf Vorschläge aus dem Cover + Farbrad, mit Live-Vorschau |
| Jekyll-Build + 29 MB scp je Änderung | Änderung ist sofort live, kein Deploy |
| Ruby + Python + Bash (macOS-Skripte) | ein Python-Dienst |
Die öffentlichen Adressen sind dieselben geblieben (/{jahr}/{titel}/),
die alten /reviews/12-Adressen leiten weiter.
Bücher und Teilwerke
Ein Eintrag muss kein ganzes Buch sein. Eine Kurzgeschichte oder ein Aufsatz aus einem Sammelband ist ein Eintrag wie jeder andere — eigene Adresse, eigenes Lesedatum, eigene Besprechung — und zeigt über Aus welchem Band? auf den Band, aus dem er stammt. In der Jahresliste stehen Bücher und Teile gemeinsam und chronologisch; getrennt wird nur gezählt:
2026: I read 42 books, 7 stories and 3 essays
Den Band selbst musst du dafür nicht gelesen haben. Ein Eintrag mit dem Stand nur als Quelle erfasst steht in keiner Liste und in keiner Zählung, hat aber eine Seite — dort sammeln sich die Teile, die du daraus gelesen hast. Ein solcher Band braucht auch kein Lesedatum; seine Adresse hängt dann am Erscheinungsjahr.
| Feld | wofür |
|---|---|
| Art | ganzes Buch, Kurzgeschichte oder Aufsatz |
| Aus welchem Band? | der Band, in dem das Teil steht |
| Fundstelle | 187-205 wird zu „S. 187–205"; Kap. 3 bleibt, wie es dasteht |
| Stelle im Band | nur zum Sortieren der Teile auf der Bandseite |
| Verlag, Sprache | Angaben des Bandes, die in die Zitation der Teile einfließen |
Zitation
Ein Teilwerk wird bibliografisch zitiert — auf seiner Seite (der Bandtitel führt dorthin), auf der Karte in der Liste und in der Vorschau der Pflegemaske:
In: „Theorien des digitalen Kapitalismus: Arbeit und Ökonomie, Politik und
Subjekt", Hg.: Tanja Carstensen, Simon Schaupp, Sebastian Sevignani,
S. 187–205, Suhrkamp, 2023
Die Sprache richtet sich nach dem Werk, nicht nach der Seite. Ein englischer
Aufsatz bekommt ed. by … und pp. 187–205, ein deutscher Hg.: … und
S. 187–205; die Anführungszeichen folgen mit. Gewählt wird das am Eintrag —
und wer einen Band auswählt, übernimmt dessen Sprache gleich mit, sodass die
Einstellung einmal am Sammelband genügt.
Alles Fehlende fällt still weg. Für eine Kurzgeschichte aus einem Erzählband ohne Herausgeber, als EPUB ohne Seitenzahlen, bleibt:
In: „Erzählungen", Kap. 3, Diogenes, 2001
Hell und dunkel
Öffentliche Seiten und Pflegeoberfläche folgen prefers-color-scheme, ohne
Umschalter. Die Farbe eines Buchs kommt aus seinem Cover und ist oft dunkel;
als Linkfarbe auf dunklem Grund wird sie deshalb aufgehellt
(render.on_dark), und die Karten bekommen beide Tönungen als CSS-Variablen
mit — umgeschaltet wird in der Stildatei, nicht im Template.
Öffentlich und privat
Alles, was in der Oberfläche als privat ausgezeichnet ist (Format, Notizen),
erscheint auf keiner öffentlichen Seite. Das ist nicht nur eine Frage der
Templates: db.PUBLIC_COLUMNS bestimmt, welche Spalten für die öffentlichen
Seiten überhaupt aus der Datenbank geholt werden — die privaten liegen dort gar
nicht erst im Objekt. tests/test_privat.py hält das fest.
Loslegen
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m app.cli benutzer thee # Konto anlegen
./dev.sh # http://127.0.0.1:42311
Alten Bestand übernehmen
.venv/bin/python -m migrate.import_jekyll /pfad/zum/alten/books-repo
Liest die Markdown-Dateien samt Covern ein, behält Adressen und Lesedaten,
übernimmt die _redirects und meldet am Ende, was auffällig war (Bücher ohne
Cover, Cover ohne Buch, unbekannte Formatangaben). Lässt sich gefahrlos
wiederholen — vorhandene Bücher werden aktualisiert, nicht verdoppelt.
Betrieb
echo "READLIST_SESSION_SECRET=$(openssl rand -hex 32)" > .env
podman-compose up -d --build
Der Dienst lauscht auf 127.0.0.1:8411; davor gehört ein Apache-vhost mit
ProxyPass /. Datenbank, Cover und Sicherungen
liegen im Volume readlist-data, also außerhalb des Repos: Ein Update ist
git pull und ein Neustart, die Bilder wandern nie durch die Leitung.
Sicherung ziehen (täglich per cron oder systemd-timer sinnvoll):
podman exec readlist python -m app.cli sicherung
Umgebungsvariablen
| Variable | Vorgabe | Bedeutung |
|---|---|---|
READLIST_DATA |
./data |
wo Datenbank und Cover liegen |
READLIST_SESSION_SECRET |
zufällig je Start | in der Produktion fest setzen |
READLIST_SESSION_HTTPS |
false |
Sitzungscookie nur über HTTPS |
READLIST_TITLE |
thee’s book tracker |
Name im Seitenkopf |
Schema nachziehen
app/schema.sql legt nur an, was fehlt (CREATE TABLE IF NOT EXISTS) — sobald
sich eine bestehende Tabelle ändert, reicht das nicht. Dafür steht in
app/migrations.py je Schemastand ein Schritt; der erreichte Stand liegt in
meta.schema_version, und der Dienst zieht beim Start selbst nach.
Wer eine Spalte ergänzt, erhöht CURRENT_VERSION und schreibt den Schritt
zusammen — läuft der Dienst mit erhöhter Nummer, aber ohne Schritt, bricht er
mit einer Meldung ab, statt die Version stillschweigend als erledigt zu
vermerken und den Umbau für immer zu verschlucken.
| Stand | was er tut |
|---|---|
| 2 | Teilwerke (kind, parent_id, part_position) und der Stand reference. Baut books neu, weil SQLite eine CHECK-Bedingung nicht ändern kann |
| 3 | Zitation: publisher, language, locator — nur angehängte Spalten |
Grenzen, bewusst gesetzt
- Keine Metadatensuche im Netz. Titel, Verfasser und Jahr tippst du selbst.
Die einzige Stelle, an der der Server nach außen spricht, ist das Holen einer
Bild-Adresse, die du selbst eingefügt hast (
covers.fetch_url) — mit Prüfung gegen Adressen im inneren Netz. - Keine Fremdbibliotheken im Browser. Farbrad, Filter und Vorschau sind eigenes JavaScript; ohne JavaScript bleiben alle Seiten benutzbar, nur die Filterleiste versteckt sich.
Tests
.venv/bin/python -m pytest tests -q
Herkunft
Angefangen hat readlist als Fork von alexwlchan/books.alexwlchan.net
(Jekyll, MIT-Lizenz, © 2022 Alex Chan). Der Dienst hier ist neu geschrieben; aus dem Original stammen
noch das Buch-Symbol (app/static/book.svg, book_white.svg) und die Papiertextur
app/static/lightpaperfibers.jpg (von Subtle Patterns).
Der Lizenztext des Originals liegt in LICENSE-books.alexwlchan.net.