Datenbank von SQLite auf Postgres umgestellt

Verbindung über KVER_DSN; Tabelle user heißt jetzt account (reserviertes
Wort in Postgres). kver import-sqlite übernimmt die alte Datei einmalig in
einer Transaktion.

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-09-26 22:22:07 +02:00
co-authored by Claude Opus 5.5
parent c6ab082f8c
commit 2714fdb1a5
15 changed files with 412 additions and 301 deletions
+21 -19
View File
@@ -5,7 +5,6 @@ import (
"math/rand/v2"
"net/http"
"os"
"strings"
"time"
"github.com/go-chi/chi/v5"
@@ -37,7 +36,7 @@ func createUser(username, password string) (int64, error) {
for i := 0; i < 5; i++ {
uid := int64(rand.IntN(99999999)) + 1
_, lastErr = db.Exec(
`INSERT INTO user (uid, created_at, username, password) VALUES (?, ?, ?, ?)`,
`INSERT INTO account (uid, created_at, username, password) VALUES ($1, $2, $3, $4)`,
uid, now, username, hash,
)
if lastErr == nil {
@@ -45,9 +44,12 @@ func createUser(username, password string) (int64, error) {
}
// username ist ebenfalls UNIQUE: hat DER kollidiert (Rennen mit der
// Vorab-Prüfung des Aufrufers), hilft kein Retry -> gezielt melden.
if strings.Contains(lastErr.Error(), "user.username") {
if isUniqueViolation(lastErr, "account_username_key") {
return 0, errUsernameTaken
}
if !isUniqueViolation(lastErr, "account_uid_key") {
return 0, lastErr
}
}
return 0, lastErr
}
@@ -55,7 +57,7 @@ func createUser(username, password string) (int64, error) {
// idFromUsername liefert die interne id oder 0, wenn der Nutzer nicht existiert.
func idFromUsername(username string) int64 {
var id int64
if err := db.QueryRow(`SELECT id FROM user WHERE username = ?`, username).Scan(&id); err != nil {
if err := db.QueryRow(`SELECT id FROM account WHERE username = $1`, username).Scan(&id); err != nil {
return 0
}
return id
@@ -72,7 +74,7 @@ func handleUserPage(w http.ResponseWriter, r *http.Request) error {
var uid, createdAt int64
var avatar string
err := db.QueryRow(`SELECT uid, created_at, avatar FROM user WHERE username = ?`, username).Scan(&uid, &createdAt, &avatar)
err := db.QueryRow(`SELECT uid, created_at, avatar FROM account WHERE username = $1`, username).Scan(&uid, &createdAt, &avatar)
if err != nil {
return errUserNotFound
}
@@ -89,7 +91,7 @@ func handleUserPage(w http.ResponseWriter, r *http.Request) error {
// registrierte Nutzer und nicht-gelöschte Beiträge.
func handleStats(w http.ResponseWriter, r *http.Request) {
var users, entries int64
db.QueryRow(`SELECT COUNT(*) FROM user`).Scan(&users)
db.QueryRow(`SELECT COUNT(*) FROM account`).Scan(&users)
db.QueryRow(`SELECT COUNT(*) FROM entry WHERE deleted = 0`).Scan(&entries)
writeJSON(w, http.StatusOK, map[string]any{
"users": users,
@@ -103,7 +105,7 @@ func handleUserInfo(w http.ResponseWriter, r *http.Request) error {
var createdAt, lastLogin sql.NullInt64
var username, avatar string
err := db.QueryRow(
`SELECT created_at, last_login, username, avatar FROM user WHERE uid = ?`, uid,
`SELECT created_at, last_login, username, avatar FROM account WHERE uid = $1`, uid,
).Scan(&createdAt, &lastLogin, &username, &avatar)
if err != nil {
return errUserNotFound
@@ -129,7 +131,7 @@ func handleUserRename(w http.ResponseWriter, r *http.Request) error {
}
var current string
if err := db.QueryRow(`SELECT username FROM user WHERE uid = ?`, uid).Scan(&current); err != nil {
if err := db.QueryRow(`SELECT username FROM account WHERE uid = $1`, uid).Scan(&current); err != nil {
return errUserNotFound
}
if name == current {
@@ -139,9 +141,9 @@ func handleUserRename(w http.ResponseWriter, r *http.Request) error {
return errUsernameTaken
}
if _, err := db.Exec(`UPDATE user SET username = ? WHERE uid = ?`, name, uid); err != nil {
if _, err := db.Exec(`UPDATE account SET username = $1 WHERE uid = $2`, name, uid); err != nil {
// Rennen zwischen Vorab-Prüfung und UPDATE: UNIQUE-Constraint -> 409.
if strings.Contains(err.Error(), "user.username") {
if isUniqueViolation(err, "account_username_key") {
return errUsernameTaken
}
return Internal(err)
@@ -171,9 +173,9 @@ func handleSetAvatar(w http.ResponseWriter, r *http.Request) error {
}
var old string
db.QueryRow(`SELECT avatar FROM user WHERE uid = ?`, uid).Scan(&old)
db.QueryRow(`SELECT avatar FROM account WHERE uid = $1`, uid).Scan(&old)
if _, err := db.Exec(`UPDATE user SET avatar = ? WHERE uid = ?`, stored, uid); err != nil {
if _, err := db.Exec(`UPDATE account SET avatar = $1 WHERE uid = $2`, stored, uid); err != nil {
os.Remove(stored)
return Internal(err)
}
@@ -193,7 +195,7 @@ func handleUserDelete(w http.ResponseWriter, r *http.Request) error {
uid := uidFromContext(r.Context())
var hash []byte
if err := db.QueryRow(`SELECT password FROM user WHERE uid = ?`, uid).Scan(&hash); err != nil {
if err := db.QueryRow(`SELECT password FROM account WHERE uid = $1`, uid).Scan(&hash); err != nil {
return errUserNotFound
}
@@ -204,7 +206,7 @@ func handleUserDelete(w http.ResponseWriter, r *http.Request) error {
// Mediendateien der Beiträge (und das Profilbild) einsammeln, bevor die
// Pfade geleert bzw. die user-Zeile gelöscht wird.
var media []string
if rows, err := db.Query(`SELECT filepath FROM entry WHERE uid = ?`, uid); err == nil {
if rows, err := db.Query(`SELECT filepath FROM entry WHERE uid = $1`, uid); err == nil {
for rows.Next() {
var fp string
if rows.Scan(&fp) == nil && fp != "" {
@@ -214,7 +216,7 @@ func handleUserDelete(w http.ResponseWriter, r *http.Request) error {
rows.Close()
}
var avatar string
if db.QueryRow(`SELECT avatar FROM user WHERE uid = ?`, uid).Scan(&avatar) == nil && avatar != "" {
if db.QueryRow(`SELECT avatar FROM account WHERE uid = $1`, uid).Scan(&avatar) == nil && avatar != "" {
media = append(media, avatar)
}
@@ -223,10 +225,10 @@ func handleUserDelete(w http.ResponseWriter, r *http.Request) error {
return Internal(err)
}
for _, q := range []string{
`DELETE FROM session WHERE uid = ?`,
`DELETE FROM vote WHERE uid = ?`,
`UPDATE entry SET deleted = 1, content = '', filepath = '', uid = 0 WHERE uid = ?`,
`DELETE FROM user WHERE uid = ?`,
`DELETE FROM session WHERE uid = $1`,
`DELETE FROM vote WHERE uid = $1`,
`UPDATE entry SET deleted = 1, content = '', filepath = '', uid = 0 WHERE uid = $1`,
`DELETE FROM account WHERE uid = $1`,
} {
if _, err := tx.Exec(q, uid); err != nil {
tx.Rollback()