Linkvorschau für den ersten Link eines Beitrags

Der Server holt zum ersten Link Titel, Seiten- bzw. Kanalname und ein
kleines Vorschaubild (320 px) und speichert das Bild lokal; der Browser
lädt nur von uns, Besucher-IPs gehen nie an die verlinkte Seite.
YouTube über oEmbed (alle Linkformen kanonisch als watch?v=<id>), alle
übrigen Seiten über OpenGraph bzw. <title>. Abruf asynchron in einem
Worker, eine Zeile je Link in link_preview, Auffrischen nach 30 Tagen.

Der Abruf-Client lässt nur öffentliche IPs auf Port 80/443 zu, geprüft
nach DNS-Auflösung und bei jeder Weiterleitung (SSRF). Bestehende
Beiträge: kver link-previews. Neue Abhängigkeit golang.org/x/net, damit
go 1.26. DEV bekommt ein beschreibbares Volume für die Vorschaubilder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
irrlicht
2026-10-06 18:47:21 +02:00
co-authored by Claude Opus 5.5
parent a8f392edba
commit 26b0182391
15 changed files with 1052 additions and 26 deletions
+15
View File
@@ -47,6 +47,8 @@ CREATE TABLE IF NOT EXISTS entry (
bump_count BIGINT NOT NULL DEFAULT 0,
last_bump BIGINT NOT NULL DEFAULT 0
);
-- Kanonische URL des ersten Links im Inhalt (previewKey), '' ohne Link.
ALTER TABLE entry ADD COLUMN IF NOT EXISTS link_url TEXT NOT NULL DEFAULT '';
CREATE TABLE IF NOT EXISTS vote (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -93,6 +95,19 @@ CREATE TABLE IF NOT EXISTS password_reset (
expires BIGINT NOT NULL
);
-- Linkvorschauen, eine Zeile je kanonischer URL (siehe preview.go), geteilt
-- von allen Beiträgen mit diesem Link. status: pending (wird geholt), ok,
-- none (keine Vorschau möglich). thumb ist das lokal gespeicherte Bild.
CREATE TABLE IF NOT EXISTS link_preview (
url TEXT PRIMARY KEY,
kind TEXT NOT NULL DEFAULT '',
title TEXT NOT NULL DEFAULT '',
site TEXT NOT NULL DEFAULT '',
thumb TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'pending',
fetched_at BIGINT NOT NULL DEFAULT 0
);
-- Indizes für die häufigen Zugriffspfade; ohne sie werden Feed (ORDER BY
-- last_activity), Thread (WHERE reply_to) und Vote-Zähler (WHERE pid) mit
-- wachsender Tabelle zu Full-Table-Scans. username/session.value/vote(uid,pid)