Frontend+API: Namen ändern (POST /user/rename)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -221,6 +221,52 @@ func TestVoteRequiresAuthAndValidMode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameUser(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
alice := registerAndLogin(t, srv, "alice")
|
||||
registerAndLogin(t, srv, "bob")
|
||||
|
||||
// Zu kurz -> 400.
|
||||
resp := postForm(t, alice, srv.URL+"/user/rename", url.Values{"user": {"ab"}})
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("zu kurz: erwartet 400, bekam %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Bereits vergeben -> 409.
|
||||
resp = postForm(t, alice, srv.URL+"/user/rename", url.Values{"user": {"bob"}})
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusConflict {
|
||||
t.Fatalf("vergeben: erwartet 409, bekam %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Gültig -> 200 + neuer Name.
|
||||
resp = postForm(t, alice, srv.URL+"/user/rename", url.Values{"user": {"alice2"}})
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("rename: erwartet 200, bekam %d", resp.StatusCode)
|
||||
}
|
||||
var out struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&out)
|
||||
if out.Username != "alice2" {
|
||||
t.Fatalf("erwarteter Name alice2, bekam %q", out.Username)
|
||||
}
|
||||
|
||||
// Profil unter dem neuen Namen erreichbar, alter Name weg.
|
||||
r2, _ := http.Get(srv.URL + "/u/alice2/info")
|
||||
r2.Body.Close()
|
||||
if r2.StatusCode != http.StatusOK {
|
||||
t.Fatalf("/u/alice2/info: erwartet 200, bekam %d", r2.StatusCode)
|
||||
}
|
||||
r3, _ := http.Get(srv.URL + "/u/alice/info")
|
||||
r3.Body.Close()
|
||||
if r3.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("/u/alice/info: erwartet 404, bekam %d", r3.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
alice := registerAndLogin(t, srv, "alice")
|
||||
|
||||
@@ -49,6 +49,7 @@ func routes() http.Handler {
|
||||
r.Post("/entry/create", handleCreateEntry)
|
||||
r.Post("/entry/{pid}/vote", handleVote)
|
||||
r.Get("/user/info", handleUserInfo)
|
||||
r.Post("/user/rename", handleUserRename)
|
||||
r.Post("/user/delete", handleUserDelete)
|
||||
})
|
||||
|
||||
|
||||
@@ -78,6 +78,38 @@ func handleUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// handleUserRename benennt den eingeloggten Account um. Nichts außer der user-Zeile
|
||||
// referenziert den username (alles hängt an der uid), daher genügt ein UPDATE.
|
||||
func handleUserRename(w http.ResponseWriter, r *http.Request) {
|
||||
uid := uidFromContext(r.Context())
|
||||
|
||||
name := sanitizeUsername(r.FormValue("user"))
|
||||
if len(name) < 3 {
|
||||
writeError(w, http.StatusBadRequest, "Der Nutzername ist zu kurz.")
|
||||
return
|
||||
}
|
||||
|
||||
var current string
|
||||
if err := db.QueryRow(`SELECT username FROM user WHERE uid = ?`, uid).Scan(¤t); err != nil {
|
||||
writeError(w, http.StatusNotFound, "Nutzer nicht gefunden")
|
||||
return
|
||||
}
|
||||
if name == current {
|
||||
writeError(w, http.StatusBadRequest, "Das ist bereits dein Name.")
|
||||
return
|
||||
}
|
||||
if idFromUsername(name) != 0 {
|
||||
writeError(w, http.StatusConflict, "Der Nutzername wird bereits verwendet.")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := db.Exec(`UPDATE user SET username = ? WHERE uid = ?`, name, uid); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Umbenennen fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]string{"username": name})
|
||||
}
|
||||
|
||||
// handleUserDelete löscht den eingeloggten Account dauerhaft, nach Bestätigung
|
||||
// durch das Passwort (Formularfeld pass1). Beiträge, Votes und Sessions werden
|
||||
// in einer Transaktion mitgelöscht; zugehörige Mediendateien best effort entfernt.
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
// Nutzername aus dem Pfad /u/<name>.
|
||||
const username = decodeURIComponent(location.pathname.split("/")[2] || "");
|
||||
|
||||
document.getElementById("rename-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await api.post("/user/rename", new FormData(e.target));
|
||||
if (res.ok) {
|
||||
location.href = `/u/${encodeURIComponent(res.data.username)}`;
|
||||
} else {
|
||||
document.getElementById("rename-msg").textContent =
|
||||
res.data.error || "Umbenennen fehlgeschlagen.";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("delete-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
if (!confirm("Konto wirklich unwiderruflich löschen?")) return;
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
<!-- Nur für den Eigentümer sichtbar -->
|
||||
<section id="settings" hidden>
|
||||
<h2>Einstellungen</h2>
|
||||
<details>
|
||||
<summary>Name ändern</summary>
|
||||
<form id="rename-form">
|
||||
<input name="user" type="text" placeholder="Neuer Nutzername" autocomplete="username">
|
||||
<button type="submit">Namen ändern</button>
|
||||
</form>
|
||||
<p class="msg" id="rename-msg"></p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Konto löschen</summary>
|
||||
<form id="delete-form">
|
||||
|
||||
Reference in New Issue
Block a user