Files
Gardomatic/internal/web/gardens.go
T
kleiax 904d14b64c
CI / test (push) Canceled after 0s
Initial commit
2026-09-12 22:22:17 +02:00

260 lines
8.2 KiB
Go

package web
import (
"errors"
"net/http"
"strings"
"gardomatic.kleiax.de/lib/client"
)
type gardenForm struct {
CSRFToken string `form:"csrf_token"`
Name string `form:"name"`
Description string `form:"description"`
ImageData string `form:"image_data"`
ImageID int `form:"image_id"`
Errors map[string]string
}
type gardenEditForms struct {
Garden gardenForm
Invite inviteForm
}
type gardenRoleSettingsForm struct {
Name string `form:"name"`
Permissions []string `form:"permissions"`
}
func (app *application) gardens(w http.ResponseWriter, r *http.Request) {
apiClient := client.FromContext(r.Context())
gardens, _, err := apiClient.Gardens(r.Context())
if err != nil {
app.handleAPIError(w, r, err)
return
}
if r.URL.Query().Get("auto") == "1" && len(gardens) == 1 {
http.Redirect(w, r, webPath("garden.dashboard", gardens[0].ID), http.StatusSeeOther)
return
}
data := app.newTemplateData(r)
data.Filters = collectionFilters(r, "q", "role", "sort")
data.Gardens = filterAndSortGardens(gardens, data.Filters)
data.Gardens, data.Pagination = paginateCollection(r, data.Gardens)
app.render(w, http.StatusOK, "gardens.tmpl", data)
}
func (app *application) gardenCreate(w http.ResponseWriter, r *http.Request) {
data := app.newTemplateData(r)
data.Form = gardenForm{Errors: make(map[string]string)}
app.render(w, http.StatusOK, "garden_new.tmpl", data)
}
func (app *application) gardenCreatePost(w http.ResponseWriter, r *http.Request) {
var form gardenForm
if err := app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.Name = strings.TrimSpace(form.Name)
form.Description = strings.TrimSpace(form.Description)
form.Errors = make(map[string]string)
if form.Name == "" {
form.Errors["name"] = "Ein Name ist erforderlich."
}
apiClient := client.FromContext(r.Context())
if len(form.Errors) == 0 {
input := client.CreateGardenInput{Name: form.Name, Description: form.Description, ImageData: form.ImageData}
if form.ImageID > 0 {
imageID := form.ImageID
input.ImageID = &imageID
}
garden, _, err := apiClient.CreateGarden(r.Context(), input)
if err == nil {
http.Redirect(w, r, webPath("plants", garden.ID), http.StatusSeeOther)
return
}
var apiError *client.APIError
if errors.As(err, &apiError) && apiError.StatusCode == http.StatusUnprocessableEntity {
form.Errors = apiError.Validation
} else {
app.handleAPIError(w, r, err)
return
}
}
data := app.newTemplateData(r)
data.Form = form
app.render(w, http.StatusUnprocessableEntity, "garden_new.tmpl", data)
}
func (app *application) gardenEdit(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
app.renderGardenEdit(w, r, gardenID, http.StatusOK, gardenEditForms{})
}
func (app *application) gardenEditPost(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
var form gardenForm
if err := app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.Name, form.Description, form.Errors = strings.TrimSpace(form.Name), strings.TrimSpace(form.Description), map[string]string{}
if form.Name == "" {
form.Errors["name"] = "Ein Name ist erforderlich."
}
apiClient := client.FromContext(r.Context())
if len(form.Errors) == 0 {
name, description, imageData := form.Name, form.Description, form.ImageData
input := client.UpdateGardenInput{Name: &name, Description: &description, ImageData: &imageData}
if form.ImageID > 0 {
imageID := form.ImageID
input.ImageID = &imageID
}
_, _, updateErr := apiClient.UpdateGarden(r.Context(), gardenID, input)
if updateErr == nil {
http.Redirect(w, r, webPath("gardens"), http.StatusSeeOther)
return
}
var apiError *client.APIError
if errors.As(updateErr, &apiError) && apiError.StatusCode == http.StatusUnprocessableEntity {
form.Errors = apiError.Validation
} else {
app.handleAPIError(w, r, updateErr)
return
}
}
app.renderGardenEdit(w, r, gardenID, http.StatusUnprocessableEntity, gardenEditForms{Garden: form})
}
func (app *application) gardenDeletePost(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
if _, err = client.FromContext(r.Context()).DeleteGarden(r.Context(), gardenID); err != nil {
app.handleAPIError(w, r, err)
return
}
http.Redirect(w, r, webPath("gardens"), http.StatusSeeOther)
}
func (app *application) gardenRoleSettingsPost(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
var form gardenRoleSettingsForm
if err = app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
if _, _, err = client.FromContext(r.Context()).UpdateGardenRoleSettings(r.Context(), gardenID, form.Name, form.Permissions); err != nil {
app.handleAPIError(w, r, err)
return
}
http.Redirect(w, r, webPath("garden.edit", gardenID)+"#garden-role-editor", http.StatusSeeOther)
}
func (app *application) gardenRoleCreatePost(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
var form roleSettingsForm
if err = app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
_, _, err = client.FromContext(r.Context()).CreateGardenRole(r.Context(), gardenID, client.RoleInput{Name: strings.TrimSpace(form.Name), Label: strings.TrimSpace(form.Label), Permissions: form.Permissions})
if err != nil {
app.handleAPIError(w, r, err)
return
}
http.Redirect(w, r, webPath("garden.edit", gardenID)+"#garden-role-editor", http.StatusSeeOther)
}
func (app *application) gardenRoleDeletePost(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
var form roleSettingsForm
if err = app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
if _, err = client.FromContext(r.Context()).DeleteGardenRole(r.Context(), gardenID, form.Name); err != nil {
app.handleAPIError(w, r, err)
return
}
http.Redirect(w, r, webPath("garden.edit", gardenID)+"#garden-role-editor", http.StatusSeeOther)
}
func (app *application) renderGardenEdit(w http.ResponseWriter, r *http.Request, gardenID, status int, forms gardenEditForms) {
apiClient := client.FromContext(r.Context())
garden, _, err := apiClient.Garden(r.Context(), gardenID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
if forms.Garden.Errors == nil {
forms.Garden = gardenForm{Name: garden.Name, Description: garden.Description, ImageData: garden.ImageData, Errors: map[string]string{}}
if garden.ImageID != nil {
forms.Garden.ImageID = *garden.ImageID
}
}
if forms.Invite.Errors == nil {
forms.Invite = inviteForm{Role: "member", Errors: map[string]string{}}
}
members, _, err := apiClient.GardenMembers(r.Context(), gardenID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
invites := []client.GardenInvite{}
roleSettings := []client.GardenRoleSetting{}
gardenRoles := []client.Role{}
if garden.Can("members:write") {
invites, _, err = apiClient.GardenInvites(r.Context(), gardenID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
roleSettings, _, err = apiClient.GardenRoleSettings(r.Context(), gardenID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
for _, setting := range roleSettings {
gardenRoles = append(gardenRoles, setting.Role)
}
}
data := app.newTemplateData(r)
data.Garden, data.Members, data.Invites, data.Form = &garden, members, invites, forms
data.GardenRoles, data.GardenRoleSettings = gardenRoles, roleSettings
data.GardenPermissions = gardenPermissionOptions()
if garden.Can("garden:delete") {
data.RoleEditors = []roleEditorData{gardenOverrideRoleEditor(gardenID, roleSettings, data.GardenPermissions, data.CSRFToken)}
}
data.Images, err = app.loadImageLibrary(r, gardenID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
app.render(w, status, "garden_form.tmpl", data)
}