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

301 lines
9.6 KiB
Go

package api
import (
"fmt"
"net/http"
"strings"
"gardomatic.kleiax.de/internal/platform/validate"
"gardomatic.kleiax.de/internal/storage"
)
type speciesTaskTemplateInput struct {
Title *string `json:"title"`
Description *string `json:"description"`
TriggerType *storage.TaskTriggerType `json:"trigger_type"`
MonthFrom *int `json:"month_from"`
DayFrom *int `json:"day_from"`
ClearDayFrom bool `json:"clear_day_from"`
MonthTo *int `json:"month_to"`
DayTo *int `json:"day_to"`
ClearDayTo bool `json:"clear_day_to"`
OffsetDaysFrom *int `json:"offset_days_from"`
OffsetDaysTo *int `json:"offset_days_to"`
IntervalDays *int `json:"interval_days"`
ClearIntervalDays bool `json:"clear_interval_days"`
TriggerOffset *int `json:"trigger_offset"`
TriggerOffsetUnit *storage.TaskDurationUnit `json:"trigger_offset_unit"`
Duration *int `json:"duration"`
DurationUnit *storage.TaskDurationUnit `json:"duration_unit"`
Recurrence *storage.TaskRecurrence `json:"recurrence"`
RecurrenceInterval *int `json:"recurrence_interval"`
Priority *int `json:"priority"`
Active *bool `json:"active"`
}
func (input speciesTaskTemplateInput) apply(template *storage.SpeciesTaskTemplate) {
if input.Title != nil {
template.Title = strings.TrimSpace(*input.Title)
}
if input.Description != nil {
template.Description = strings.TrimSpace(*input.Description)
}
if input.TriggerType != nil {
template.TriggerType = *input.TriggerType
}
if input.MonthFrom != nil {
template.MonthFrom = positivePointer(input.MonthFrom)
}
if input.DayFrom != nil {
template.DayFrom = positivePointer(input.DayFrom)
}
if input.ClearDayFrom {
template.DayFrom = nil
}
if input.MonthTo != nil {
template.MonthTo = positivePointer(input.MonthTo)
}
if input.DayTo != nil {
template.DayTo = positivePointer(input.DayTo)
}
if input.ClearDayTo {
template.DayTo = nil
}
if input.OffsetDaysFrom != nil {
template.OffsetDaysFrom = input.OffsetDaysFrom
}
if input.OffsetDaysTo != nil {
template.OffsetDaysTo = input.OffsetDaysTo
}
if input.IntervalDays != nil {
template.IntervalDays = positivePointer(input.IntervalDays)
}
if input.ClearIntervalDays {
template.IntervalDays = nil
}
if input.TriggerOffset != nil {
template.TriggerOffset = *input.TriggerOffset
}
if input.TriggerOffsetUnit != nil {
template.TriggerOffsetUnit = *input.TriggerOffsetUnit
}
if input.Duration != nil {
template.Duration = *input.Duration
}
if input.DurationUnit != nil {
template.DurationUnit = *input.DurationUnit
}
if input.Recurrence != nil {
template.Recurrence = *input.Recurrence
}
if input.RecurrenceInterval != nil {
template.RecurrenceInterval = *input.RecurrenceInterval
}
if input.Priority != nil {
template.Priority = *input.Priority
}
if input.Active != nil {
template.Active = *input.Active
}
if template.TriggerType == storage.TaskTriggerMonthOfYear {
template.OffsetDaysFrom, template.OffsetDaysTo = nil, nil
} else {
template.MonthFrom, template.DayFrom, template.MonthTo, template.DayTo = nil, nil, nil, nil
}
}
func positivePointer(value *int) *int {
if value != nil && *value <= 0 {
return nil
}
return value
}
func (app *application) createSpeciesTaskTemplateHandler(w http.ResponseWriter, r *http.Request) {
gardenID, _ := app.readGardenIDParam(r)
speciesID, _ := app.readIDParam(r)
if _, ok := app.requireWritableSpecies(w, r, gardenID, speciesID); !ok {
return
}
var input speciesTaskTemplateInput
if err := app.readJSON(w, r, &input); err != nil {
app.badRequestResponse(w, r, err)
return
}
template := storage.SpeciesTaskTemplate{SpeciesID: speciesID, Origin: storage.TaskTemplateOriginManual, Active: true, TriggerOffsetUnit: storage.TaskDurationDay, DurationUnit: storage.TaskDurationDay, RecurrenceInterval: 1}
input.apply(&template)
if !app.validateSpeciesTaskTemplate(w, r, template) {
return
}
template, err := app.models.SpeciesTaskTemplates.Insert(template)
if err != nil {
app.respondToEntityModelError(w, r, err)
return
}
headers := make(http.Header)
headers.Set("Location", fmt.Sprintf("/v1/gardens/%d/species/%d/task-templates/%d", gardenID, speciesID, template.ID))
if err := app.writeJSON(w, http.StatusCreated, envelope{"task_template": template}, headers); err != nil {
app.serverErrorResponse(w, r, err)
}
}
func (app *application) listSpeciesTaskTemplatesHandler(w http.ResponseWriter, r *http.Request) {
gardenID, _ := app.readGardenIDParam(r)
speciesID, _ := app.readIDParam(r)
if _, err := app.models.Species.Get(gardenID, speciesID); err != nil {
app.respondToEntityModelError(w, r, err)
return
}
templates, err := app.models.SpeciesTaskTemplates.GetAllForSpecies(gardenID, speciesID)
if err != nil {
app.respondToEntityModelError(w, r, err)
return
}
if err := app.writeJSON(w, http.StatusOK, envelope{"task_templates": templates}, nil); err != nil {
app.serverErrorResponse(w, r, err)
}
}
func (app *application) showSpeciesTaskTemplateHandler(w http.ResponseWriter, r *http.Request) {
gardenID, _ := app.readGardenIDParam(r)
speciesID, _ := app.readIDParam(r)
templateID, err := app.readNamedIDParam(r, "templateID")
if err != nil {
app.notFoundResponse(w, r)
return
}
template, err := app.models.SpeciesTaskTemplates.Get(gardenID, templateID)
if err != nil || template.SpeciesID != speciesID {
if err == nil {
err = storage.ErrRecordNotFound
}
app.respondToEntityModelError(w, r, err)
return
}
if err := app.writeJSON(w, http.StatusOK, envelope{"task_template": template}, nil); err != nil {
app.serverErrorResponse(w, r, err)
}
}
func (app *application) updateSpeciesTaskTemplateHandler(w http.ResponseWriter, r *http.Request) {
gardenID, _ := app.readGardenIDParam(r)
speciesID, _ := app.readIDParam(r)
templateID, err := app.readNamedIDParam(r, "templateID")
if err != nil {
app.notFoundResponse(w, r)
return
}
species, writable := app.requireWritableSpecies(w, r, gardenID, speciesID)
if !writable {
return
}
template, err := app.models.SpeciesTaskTemplates.Get(gardenID, templateID)
if err != nil || template.SpeciesID != speciesID {
if err == nil {
err = storage.ErrRecordNotFound
}
app.respondToEntityModelError(w, r, err)
return
}
var input speciesTaskTemplateInput
if err := app.readJSON(w, r, &input); err != nil {
app.badRequestResponse(w, r, err)
return
}
input.apply(&template)
if !app.validateSpeciesTaskTemplate(w, r, template) {
return
}
writeScope := gardenID
if species.GardenID == nil {
writeScope = 0
}
template, err = app.models.SpeciesTaskTemplates.Update(writeScope, template)
if err != nil {
app.respondToEntityModelError(w, r, err)
return
}
if err := app.writeJSON(w, http.StatusOK, envelope{"task_template": template}, nil); err != nil {
app.serverErrorResponse(w, r, err)
}
}
func (app *application) deleteSpeciesTaskTemplateHandler(w http.ResponseWriter, r *http.Request) {
gardenID, _ := app.readGardenIDParam(r)
speciesID, _ := app.readIDParam(r)
templateID, err := app.readNamedIDParam(r, "templateID")
if err != nil {
app.notFoundResponse(w, r)
return
}
species, writable := app.requireWritableSpecies(w, r, gardenID, speciesID)
if !writable {
return
}
template, err := app.models.SpeciesTaskTemplates.Get(gardenID, templateID)
if err != nil || template.SpeciesID != speciesID {
if err == nil {
err = storage.ErrRecordNotFound
}
app.respondToEntityModelError(w, r, err)
return
}
writeScope := gardenID
if species.GardenID == nil {
writeScope = 0
}
if template.Origin != storage.TaskTemplateOriginManual {
template.Active = false
if _, err := app.models.SpeciesTaskTemplates.Update(writeScope, template); err != nil {
app.respondToEntityModelError(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
return
}
if err := app.models.SpeciesTaskTemplates.Delete(writeScope, templateID); err != nil {
app.respondToEntityModelError(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (app *application) requireWritableSpecies(w http.ResponseWriter, r *http.Request, gardenID, speciesID int) (storage.Species, bool) {
species, err := app.models.Species.Get(gardenID, speciesID)
if err != nil {
app.respondToEntityModelError(w, r, err)
return storage.Species{}, false
}
if species.GardenID == nil {
user, _ := app.contextGetAuthenticatedUser(r)
if !user.Can(storage.ApplicationPermissionGlobalSpeciesWrite) {
app.permissionDeniedResponse(w, r)
return storage.Species{}, false
}
return species, true
}
if *species.GardenID != gardenID {
app.respondToEntityModelError(w, r, storage.ErrRecordNotFound)
return storage.Species{}, false
}
member, _ := app.contextGetGardenMember(r)
if !member.Can(storage.GardenPermissionSpeciesWrite) {
app.permissionDeniedResponse(w, r)
return storage.Species{}, false
}
return species, true
}
func (app *application) validateSpeciesTaskTemplate(w http.ResponseWriter, r *http.Request, template storage.SpeciesTaskTemplate) bool {
v := validate.New()
storage.ValidateSpeciesTaskTemplate(v, template)
if !app.validateConfiguredPriority(w, r, v, template.Priority) {
return false
}
if !v.Valid() {
app.failedValidationResponse(w, r, v.Errors)
return false
}
return true
}