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

233 lines
8.1 KiB
Go

package web
import (
"errors"
"net/http"
"strconv"
"strings"
"gardomatic.kleiax.de/lib/client"
)
type taskTemplateForm struct {
Origin string `form:"origin"`
Title string `form:"title"`
Description string `form:"description"`
TriggerType string `form:"trigger_type"`
MonthFrom int `form:"month_from"`
DayFrom int `form:"day_from"`
MonthTo int `form:"month_to"`
DayTo int `form:"day_to"`
OffsetDaysFrom int `form:"offset_days_from"`
OffsetDaysTo int `form:"offset_days_to"`
IntervalDays int `form:"interval_days"`
TriggerOffset int `form:"trigger_offset"`
TriggerOffsetUnit string `form:"trigger_offset_unit"`
Duration int `form:"duration"`
DurationUnit string `form:"duration_unit"`
Recurrence string `form:"recurrence"`
RecurrenceInterval int `form:"recurrence_interval"`
Priority int `form:"priority"`
Active bool `form:"active"`
Errors map[string]string
}
func (app *application) taskTemplateCreate(w http.ResponseWriter, r *http.Request) {
speciesID, _ := strconv.Atoi(r.URL.Query().Get("species_id"))
if speciesID < 1 {
app.notFound(w)
return
}
app.renderTaskTemplateForm(w, r, taskTemplateForm{TriggerType: "month_of_year", DayFrom: 1, TriggerOffsetUnit: "day", DurationUnit: "day", RecurrenceInterval: 1, Active: true, Errors: map[string]string{}}, http.StatusOK, speciesID, 0)
}
func (app *application) taskTemplateEdit(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
templateID, err := app.readPathID(r, "templateID")
if err != nil {
app.notFound(w)
return
}
speciesID, _ := strconv.Atoi(r.URL.Query().Get("species_id"))
if speciesID < 1 {
app.notFound(w)
return
}
value, _, err := client.FromContext(r.Context()).SpeciesTaskTemplate(r.Context(), gardenID, speciesID, templateID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
form := taskTemplateForm{Origin: value.Origin, Title: value.Title, Description: value.Description, TriggerType: value.TriggerType, TriggerOffset: value.TriggerOffset, TriggerOffsetUnit: value.TriggerOffsetUnit, Duration: value.Duration, DurationUnit: value.DurationUnit, Recurrence: value.Recurrence, RecurrenceInterval: value.RecurrenceInterval, Priority: value.Priority, Active: value.Active, Errors: map[string]string{}}
if form.TriggerOffsetUnit == "" {
form.TriggerOffsetUnit = "day"
}
if form.DurationUnit == "" {
form.DurationUnit = "day"
}
if form.RecurrenceInterval < 1 {
form.RecurrenceInterval = 1
}
copyOptionalInt(value.MonthFrom, &form.MonthFrom)
copyOptionalInt(value.DayFrom, &form.DayFrom)
copyOptionalInt(value.MonthTo, &form.MonthTo)
copyOptionalInt(value.DayTo, &form.DayTo)
copyOptionalInt(value.OffsetDaysFrom, &form.OffsetDaysFrom)
copyOptionalInt(value.OffsetDaysTo, &form.OffsetDaysTo)
copyOptionalInt(value.IntervalDays, &form.IntervalDays)
app.renderTaskTemplateForm(w, r, form, http.StatusOK, speciesID, templateID)
}
func copyOptionalInt(source *int, destination *int) {
if source != nil {
*destination = *source
}
}
func (app *application) taskTemplateSave(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
speciesID, _ := strconv.Atoi(r.URL.Query().Get("species_id"))
if speciesID < 1 {
app.notFound(w)
return
}
templateID := 0
if id, readErr := app.readPathID(r, "templateID"); readErr == nil {
templateID = id
}
var form taskTemplateForm
if err := app.decodePostForm(r, &form); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.Title, form.Description = strings.TrimSpace(form.Title), strings.TrimSpace(form.Description)
form.Errors = map[string]string{}
if form.Title == "" {
form.Errors["title"] = "Ein Name ist erforderlich."
}
if form.TriggerType == "month_of_year" && (form.MonthFrom < 1 || form.DayFrom < 1) {
form.Errors["date"] = "Tag und Monat für „Ab“ sind erforderlich."
}
if form.Duration < 0 || form.TriggerOffset < 0 {
form.Errors["duration"] = "Werte dürfen nicht negativ sein."
}
if form.Recurrence != "" && form.RecurrenceInterval < 1 {
form.Errors["recurrence_interval"] = "Der Faktor muss mindestens 1 sein."
}
input := taskTemplateInput(form)
if len(form.Errors) == 0 {
apiClient := client.FromContext(r.Context())
var saveErr error
if templateID > 0 {
_, _, saveErr = apiClient.UpdateSpeciesTaskTemplate(r.Context(), gardenID, speciesID, templateID, input)
} else {
_, _, saveErr = apiClient.CreateSpeciesTaskTemplate(r.Context(), gardenID, speciesID, input)
}
if saveErr == nil {
returnTo := webPath("species.edit", gardenID, speciesID)
if r.Header.Get("HX-Request") == "true" {
w.Header().Set("HX-Redirect", returnTo)
w.WriteHeader(http.StatusNoContent)
return
}
http.Redirect(w, r, returnTo, http.StatusSeeOther)
return
}
var apiError *client.APIError
if errors.As(saveErr, &apiError) && apiError.StatusCode == http.StatusUnprocessableEntity {
form.Errors = apiError.Validation
} else {
app.handleAPIError(w, r, saveErr)
return
}
}
status := http.StatusUnprocessableEntity
if r.Header.Get("HX-Request") == "true" {
w.Header().Set("HX-Retarget", "#task-template-dialog-host")
w.Header().Set("HX-Reswap", "innerHTML")
status = http.StatusOK
}
app.renderTaskTemplateForm(w, r, form, status, speciesID, templateID)
}
func taskTemplateInput(form taskTemplateForm) client.SpeciesTaskTemplateInput {
title, description, trigger := form.Title, form.Description, form.TriggerType
offsetUnit, durationUnit, recurrence := form.TriggerOffsetUnit, form.DurationUnit, form.Recurrence
input := client.SpeciesTaskTemplateInput{Title: &title, Description: &description, TriggerType: &trigger, Duration: &form.Duration, DurationUnit: &durationUnit, Recurrence: &recurrence, RecurrenceInterval: &form.RecurrenceInterval, Priority: &form.Priority, Active: &form.Active, ClearIntervalDays: true}
if form.TriggerType == "month_of_year" {
input.MonthFrom = &form.MonthFrom
if form.DayFrom > 0 {
input.DayFrom = &form.DayFrom
} else {
input.ClearDayFrom = true
}
} else {
input.TriggerOffset = &form.TriggerOffset
input.TriggerOffsetUnit = &offsetUnit
input.ClearDayFrom = true
}
return input
}
func (app *application) taskTemplateDelete(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
templateID, err := app.readPathID(r, "templateID")
if err != nil {
app.notFound(w)
return
}
speciesID, _ := strconv.Atoi(r.URL.Query().Get("species_id"))
if speciesID < 1 {
app.notFound(w)
return
}
if _, err := client.FromContext(r.Context()).DeleteSpeciesTaskTemplate(r.Context(), gardenID, speciesID, templateID); err != nil {
app.handleAPIError(w, r, err)
return
}
http.Redirect(w, r, pathWithQuery(webPath("species.edit", gardenID, speciesID), "step", "tasks"), http.StatusSeeOther)
}
func (app *application) renderTaskTemplateForm(w http.ResponseWriter, r *http.Request, form taskTemplateForm, status, speciesID, templateID int) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
apiClient := client.FromContext(r.Context())
garden, _, err := apiClient.Garden(r.Context(), gardenID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
species, _, err := apiClient.Species(r.Context(), gardenID, speciesID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
priorities, _, err := apiClient.TaskPriorities(r.Context())
if err != nil {
app.handleAPIError(w, r, err)
return
}
data := app.newTemplateData(r)
data.Garden, data.Species, data.TaskPriorities, data.Form, data.SpeciesID, data.TemplateID = &garden, []client.Species{species}, priorities, form, speciesID, templateID
if r.Header.Get("HX-Request") == "true" {
app.renderTemplate(w, status, "task_template_form.tmpl", "task_template_dialog", data)
} else {
app.render(w, status, "task_template_form.tmpl", data)
}
}