301 lines
9.6 KiB
Go
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
|
|
}
|