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