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) }