50be342899
- http.Server mit Timeouts (Slowloris) und Graceful Shutdown (SIGTERM) - Adresse und DB-Pfad per Env (KVER_ADDR, KVER_DB) - Security-Header global: nosniff, X-Frame-Options, Referrer-Policy, CSP - Upload-Requests hart auf 17 MB gedeckelt (MaxBytesReader statt nur Multipart-Speichergrenze) - Indizes für Feed-, Thread- und Vote-Queries - middleware.RealIP für Logging/Rate-Limit hinter dem Reverse-Proxy - Containerfile (Multi-Stage, Alpine, non-root) + Deploy-Doku in notes/ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
2.2 KiB
Go
77 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
var db *sql.DB
|
|
|
|
const schema = `
|
|
CREATE TABLE IF NOT EXISTS user (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid INTEGER UNIQUE,
|
|
username TEXT UNIQUE,
|
|
password BLOB,
|
|
created_at INTEGER,
|
|
last_login INTEGER,
|
|
avatar TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS session (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid INTEGER,
|
|
value TEXT UNIQUE,
|
|
created_at INTEGER,
|
|
expires INTEGER,
|
|
description TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS entry (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
pid INTEGER UNIQUE NOT NULL,
|
|
uid INTEGER NOT NULL,
|
|
created_at INTEGER NOT NULL,
|
|
content TEXT NOT NULL DEFAULT '',
|
|
filepath TEXT NOT NULL DEFAULT '',
|
|
reply_to INTEGER NOT NULL DEFAULT 0,
|
|
reply_count INTEGER NOT NULL DEFAULT 0,
|
|
last_activity INTEGER NOT NULL DEFAULT 0,
|
|
deleted INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS vote (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid INTEGER,
|
|
pid INTEGER,
|
|
mode TEXT,
|
|
UNIQUE(uid, pid)
|
|
);
|
|
|
|
-- 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)
|
|
-- sind über UNIQUE bereits indiziert.
|
|
CREATE INDEX IF NOT EXISTS idx_entry_last_activity ON entry(last_activity);
|
|
CREATE INDEX IF NOT EXISTS idx_entry_reply_to ON entry(reply_to);
|
|
CREATE INDEX IF NOT EXISTS idx_entry_uid ON entry(uid);
|
|
CREATE INDEX IF NOT EXISTS idx_vote_pid ON vote(pid);
|
|
CREATE INDEX IF NOT EXISTS idx_session_uid ON session(uid);
|
|
`
|
|
|
|
func initDB(dsn string) error {
|
|
var err error
|
|
// SQLite hat genau einen Schreiber: busy_timeout wartet bei Sperren, statt
|
|
// sofort mit SQLITE_BUSY zu scheitern; WAL erlaubt parallele Leser.
|
|
db, err = sql.Open("sqlite", dsn+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(on)")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = db.Ping(); err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(schema)
|
|
return err
|
|
}
|