@@ -0,0 +1,300 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user