package main import ( "context" "errors" "fmt" "os" "strings" ) // runCommand führt einen Admin-Unterbefehl aus (kver ...). Im // Container: podman exec kver kver ... func runCommand(args []string) error { switch args[0] { case "reset-link": if len(args) != 2 { return errors.New("Verwendung: kver reset-link ") } return cmdResetLink(args[1]) case "link-previews": if len(args) != 1 { return errors.New("Verwendung: kver link-previews") } return cmdLinkPreviews() default: return fmt.Errorf("unbekannter Befehl %q (bekannt: reset-link, link-previews)", args[0]) } } // cmdResetLink erzeugt einen Passwort-Reset-Link (siehe reset.go) und gibt ihn // aus. KVER_BASE_URL (z. B. https://example.org) macht daraus einen fertigen // Link; ohne steht nur der Pfad da. func cmdResetLink(username string) error { username = sanitizeUsername(username) token, expires, err := createResetToken(username) if errors.Is(err, errUserNotFound) { return fmt.Errorf("Nutzer %q nicht gefunden", username) } else if err != nil { return err } base := strings.TrimSuffix(os.Getenv("KVER_BASE_URL"), "/") fmt.Printf("Reset-Link für %s (gültig bis %s, einmalig):\n\n %s/reset#%s\n\n", username, expires.Format("02.01.2006 15:04 MST"), base, token) fmt.Println("Ältere Reset-Links dieses Nutzers sind damit ungültig.") return nil } // cmdLinkPreviews trägt bei bestehenden Beiträgen den ersten Link nach // (entry.link_url) und holt alle offenen Vorschauen nacheinander. Läuft der // Server parallel, holt er höchstens dieselben Links noch einmal -- harmlos. func cmdLinkPreviews() error { rows, err := db.Query(`SELECT pid, content FROM entry WHERE deleted = 0 AND link_url = '' AND content LIKE '%http%'`) if err != nil { return err } links := map[int64]string{} for rows.Next() { var pid int64 var content string if err := rows.Scan(&pid, &content); err != nil { rows.Close() return err } if key := previewKey(content); key != "" { links[pid] = key } } rows.Close() if err := rows.Err(); err != nil { return err } for pid, key := range links { if _, err := db.Exec(`UPDATE entry SET link_url = $1 WHERE pid = $2`, key, pid); err != nil { return err } if _, err := db.Exec(`INSERT INTO link_preview (url) VALUES ($1) ON CONFLICT DO NOTHING`, key); err != nil { return err } } fmt.Printf("%d Beiträge mit Link nachgetragen.\n", len(links)) pending := pendingPreviews(100000) for i, key := range pending { fmt.Printf("[%d/%d] %s\n", i+1, len(pending), key) fetchPreview(context.Background(), key) } var ok, none int if err := db.QueryRow( `SELECT COUNT(*) FILTER (WHERE status = 'ok'), COUNT(*) FILTER (WHERE status = 'none') FROM link_preview`). Scan(&ok, &none); err != nil { return err } fmt.Printf("Vorschauen: %d fertig, %d ohne Vorschau.\n", ok, none) return nil }