Files
Gardomatic/internal/web/account.go
T
2026-09-14 19:58:31 +02:00

171 lines
5.9 KiB
Go

package web
import (
"errors"
"net/http"
"strings"
"gardomatic.kleiax.de/lib/client"
"github.com/julienschmidt/httprouter"
)
type accountForm struct {
CSRFToken string `form:"csrf_token"`
Name string `form:"name"`
Color string `form:"color"`
Email string `form:"email"`
CurrentPassword string `form:"current_password"`
NewPassword string `form:"new_password"`
NewPasswordConfirmation string `form:"new_password_confirmation"`
Errors map[string]string
Message string
}
func (app *application) account(w http.ResponseWriter, r *http.Request) {
user, _ := userFromContext(r.Context())
app.renderAccount(w, r, accountForm{Name: user.Name, Color: user.Color, Email: user.Email, Errors: map[string]string{}}, http.StatusOK)
}
func (app *application) accountSessionDeletePost(w http.ResponseWriter, r *http.Request) {
id := httprouter.ParamsFromContext(r.Context()).ByName("sessionID")
response, err := client.FromContext(r.Context()).DeleteAccountSession(r.Context(), id)
if err != nil {
app.handleAPIError(w, r, err)
return
}
client.ForwardCookies(w, response)
http.Redirect(w, r, webPath("account")+gardenQuerySuffix(r), http.StatusSeeOther)
}
func (app *application) accountProfilePost(w http.ResponseWriter, r *http.Request) {
var form accountForm
if err := app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.Name = strings.TrimSpace(form.Name)
form.Color = strings.TrimSpace(form.Color)
form.Errors = map[string]string{}
if form.Name == "" {
form.Errors["name"] = "Ein Name ist erforderlich."
}
if len(form.Errors) == 0 {
user, _, err := client.FromContext(r.Context()).UpdateAccountProfile(r.Context(), form.Name, form.Color)
if err == nil {
form.Name, form.Color, form.Email, form.Message = user.Name, user.Color, user.Email, "Profil gespeichert."
app.renderAccount(w, r, form, http.StatusOK)
return
}
app.copyAccountError(&form, err)
if len(form.Errors) == 0 {
app.handleAPIError(w, r, err)
return
}
}
app.renderAccount(w, r, form, http.StatusUnprocessableEntity)
}
func (app *application) accountPasswordPost(w http.ResponseWriter, r *http.Request) {
var form accountForm
if err := app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
user, _ := userFromContext(r.Context())
form.Name, form.Color, form.Email, form.Errors = user.Name, user.Color, user.Email, map[string]string{}
if form.NewPassword != form.NewPasswordConfirmation {
form.Errors["new_password_confirmation"] = "Die Passwörter stimmen nicht überein."
}
if len(form.NewPassword) < 8 {
form.Errors["new_password"] = "Mindestens 8 Zeichen erforderlich."
}
if len(form.Errors) == 0 {
_, err := client.FromContext(r.Context()).UpdateAccountPassword(r.Context(), form.CurrentPassword, form.NewPassword)
if err == nil {
form.CurrentPassword, form.NewPassword, form.NewPasswordConfirmation = "", "", ""
form.Message = "Passwort geändert."
app.renderAccount(w, r, form, http.StatusOK)
return
}
app.copyAccountError(&form, err)
if len(form.Errors) == 0 {
app.handleAPIError(w, r, err)
return
}
}
app.renderAccount(w, r, form, http.StatusUnprocessableEntity)
}
func (app *application) accountEmailPost(w http.ResponseWriter, r *http.Request) {
var form accountForm
if err := app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
user, _ := userFromContext(r.Context())
form.Name, form.Color, form.Errors = user.Name, user.Color, map[string]string{}
form.Email = strings.TrimSpace(form.Email)
if form.Email == "" {
form.Errors["email"] = "Eine E-Mail-Adresse ist erforderlich."
}
if len(form.Errors) == 0 {
_, err := client.FromContext(r.Context()).RequestAccountEmailChange(r.Context(), form.Email, form.CurrentPassword)
if err == nil {
form.CurrentPassword = ""
form.Message = "Bestätigungslink wurde an die neue Adresse gesendet."
app.renderAccount(w, r, form, http.StatusOK)
return
}
app.copyAccountError(&form, err)
if len(form.Errors) == 0 {
app.handleAPIError(w, r, err)
return
}
}
app.renderAccount(w, r, form, http.StatusUnprocessableEntity)
}
func (app *application) accountEmailConfirm(w http.ResponseWriter, r *http.Request) {
data := app.newTemplateData(r)
data.Form = acceptInviteForm{Token: r.URL.Query().Get("token")}
app.render(w, http.StatusOK, "email_confirm.tmpl", data)
}
func (app *application) accountEmailConfirmPost(w http.ResponseWriter, r *http.Request) {
var form acceptInviteForm
if err := app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
if _, _, err := client.FromContext(r.Context()).ConfirmAccountEmail(r.Context(), form.Token); err != nil {
app.handleAPIError(w, r, err)
return
}
http.Redirect(w, r, webPath("account"), http.StatusSeeOther)
}
func (app *application) renderAccount(w http.ResponseWriter, r *http.Request, form accountForm, status int) {
data := app.newTemplateData(r)
data.Form = form
if user, ok := userFromContext(r.Context()); ok {
data.AccountProtected = app.config.DemoAccountEmail != "" && strings.EqualFold(strings.TrimSpace(user.Email), app.config.DemoAccountEmail)
}
if !app.loadOptionalGarden(w, r, data) {
return
}
if !data.AccountProtected {
sessions, _, err := client.FromContext(r.Context()).AccountSessions(r.Context())
if err != nil {
app.handleAPIError(w, r, err)
return
}
data.AccountSessions = sessions
}
app.render(w, status, "account.tmpl", data)
}
func (app *application) copyAccountError(form *accountForm, err error) {
var apiError *client.APIError
if errors.As(err, &apiError) {
if apiError.StatusCode == http.StatusUnprocessableEntity {
form.Errors = apiError.Validation
}
if apiError.StatusCode == http.StatusUnauthorized {
form.Errors["current_password"] = "Das aktuelle Passwort ist falsch."
}
}
}