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) {
|
func TestDeleteUser(t *testing.T) {
|
||||||
srv := newTestServer(t)
|
srv := newTestServer(t)
|
||||||
alice := registerAndLogin(t, srv, "alice")
|
alice := registerAndLogin(t, srv, "alice")
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ func routes() http.Handler {
|
|||||||
r.Post("/entry/create", handleCreateEntry)
|
r.Post("/entry/create", handleCreateEntry)
|
||||||
r.Post("/entry/{pid}/vote", handleVote)
|
r.Post("/entry/{pid}/vote", handleVote)
|
||||||
r.Get("/user/info", handleUserInfo)
|
r.Get("/user/info", handleUserInfo)
|
||||||
|
r.Post("/user/rename", handleUserRename)
|
||||||
r.Post("/user/delete", handleUserDelete)
|
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
|
// handleUserDelete löscht den eingeloggten Account dauerhaft, nach Bestätigung
|
||||||
// durch das Passwort (Formularfeld pass1). Beiträge, Votes und Sessions werden
|
// durch das Passwort (Formularfeld pass1). Beiträge, Votes und Sessions werden
|
||||||
// in einer Transaktion mitgelöscht; zugehörige Mediendateien best effort entfernt.
|
// in einer Transaktion mitgelöscht; zugehörige Mediendateien best effort entfernt.
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
// Nutzername aus dem Pfad /u/<name>.
|
// Nutzername aus dem Pfad /u/<name>.
|
||||||
const username = decodeURIComponent(location.pathname.split("/")[2] || "");
|
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) => {
|
document.getElementById("delete-form").addEventListener("submit", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!confirm("Konto wirklich unwiderruflich löschen?")) return;
|
if (!confirm("Konto wirklich unwiderruflich löschen?")) return;
|
||||||
|
|||||||
@@ -19,6 +19,14 @@
|
|||||||
<!-- Nur für den Eigentümer sichtbar -->
|
<!-- Nur für den Eigentümer sichtbar -->
|
||||||
<section id="settings" hidden>
|
<section id="settings" hidden>
|
||||||
<h2>Einstellungen</h2>
|
<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>
|
<details>
|
||||||
<summary>Konto löschen</summary>
|
<summary>Konto löschen</summary>
|
||||||
<form id="delete-form">
|
<form id="delete-form">
|
||||||
|
|||||||
Reference in New Issue
Block a user