196 lines
5.5 KiB
Go
196 lines
5.5 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gardomatic.kleiax.de/internal/platform/validate"
|
|
"gardomatic.kleiax.de/internal/storage"
|
|
"github.com/julienschmidt/httprouter"
|
|
)
|
|
|
|
type plantLocationInput struct {
|
|
LocationID *int `json:"location_id"`
|
|
Quantity *int `json:"quantity"`
|
|
PlantedAt *time.Time `json:"planted_at"`
|
|
ClearPlantedAt bool `json:"clear_planted_at"`
|
|
RemovedAt *time.Time `json:"removed_at"`
|
|
Notes *string `json:"notes"`
|
|
}
|
|
|
|
func (input plantLocationInput) apply(assignment *storage.PlantLocation) {
|
|
if input.LocationID != nil {
|
|
assignment.LocationID = *input.LocationID
|
|
}
|
|
if input.Quantity != nil {
|
|
assignment.Quantity = *input.Quantity
|
|
}
|
|
if input.PlantedAt != nil {
|
|
assignment.PlantedAt = input.PlantedAt
|
|
}
|
|
if input.ClearPlantedAt {
|
|
assignment.PlantedAt = nil
|
|
}
|
|
if input.RemovedAt != nil {
|
|
assignment.RemovedAt = input.RemovedAt
|
|
}
|
|
if input.Notes != nil {
|
|
assignment.Notes = strings.TrimSpace(*input.Notes)
|
|
}
|
|
}
|
|
|
|
func (app *application) createPlantLocationHandler(w http.ResponseWriter, r *http.Request) {
|
|
gardenID, _ := app.readGardenIDParam(r)
|
|
plantID, err := app.readIDParam(r)
|
|
if err != nil {
|
|
app.notFoundResponse(w, r)
|
|
return
|
|
}
|
|
if _, err := app.models.Plants.Get(gardenID, plantID); err != nil {
|
|
app.respondToEntityModelError(w, r, err)
|
|
return
|
|
}
|
|
var input plantLocationInput
|
|
if err := app.readJSON(w, r, &input); err != nil {
|
|
app.badRequestResponse(w, r, err)
|
|
return
|
|
}
|
|
assignment := storage.PlantLocation{PlantID: plantID, Quantity: 1}
|
|
input.apply(&assignment)
|
|
if !app.validatePlantLocationForGarden(w, r, gardenID, assignment) {
|
|
return
|
|
}
|
|
assignment, err = app.models.PlantLocations.Insert(gardenID, assignment)
|
|
if err != nil {
|
|
app.respondToEntityModelError(w, r, err)
|
|
return
|
|
}
|
|
headers := make(http.Header)
|
|
headers.Set("Location", fmt.Sprintf("/v1/gardens/%d/plants/%d/locations/%d", gardenID, plantID, assignment.ID))
|
|
if err := app.writeJSON(w, http.StatusCreated, envelope{"plant_location": assignment}, headers); err != nil {
|
|
app.serverErrorResponse(w, r, err)
|
|
}
|
|
}
|
|
|
|
func (app *application) listPlantLocationsHandler(w http.ResponseWriter, r *http.Request) {
|
|
gardenID, _ := app.readGardenIDParam(r)
|
|
plantID, err := app.readIDParam(r)
|
|
if err != nil {
|
|
app.notFoundResponse(w, r)
|
|
return
|
|
}
|
|
if _, err := app.models.Plants.Get(gardenID, plantID); err != nil {
|
|
app.respondToEntityModelError(w, r, err)
|
|
return
|
|
}
|
|
assignments, err := app.models.PlantLocations.GetAllForPlant(gardenID, plantID)
|
|
if err != nil {
|
|
app.serverErrorResponse(w, r, err)
|
|
return
|
|
}
|
|
if err := app.writeJSON(w, http.StatusOK, envelope{"plant_locations": assignments}, nil); err != nil {
|
|
app.serverErrorResponse(w, r, err)
|
|
}
|
|
}
|
|
|
|
func (app *application) updatePlantLocationHandler(w http.ResponseWriter, r *http.Request) {
|
|
gardenID, _ := app.readGardenIDParam(r)
|
|
plantID, assignmentID, ok := app.readPlantLocationIDs(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
assignment, err := app.models.PlantLocations.Get(gardenID, assignmentID)
|
|
if err != nil || assignment.PlantID != plantID {
|
|
if err == nil {
|
|
err = storage.ErrRecordNotFound
|
|
}
|
|
app.respondToEntityModelError(w, r, err)
|
|
return
|
|
}
|
|
var input plantLocationInput
|
|
if err := app.readJSON(w, r, &input); err != nil {
|
|
app.badRequestResponse(w, r, err)
|
|
return
|
|
}
|
|
input.apply(&assignment)
|
|
if !app.validatePlantLocationForGarden(w, r, gardenID, assignment) {
|
|
return
|
|
}
|
|
assignment, err = app.models.PlantLocations.Update(gardenID, assignment)
|
|
if err != nil {
|
|
app.respondToEntityModelError(w, r, err)
|
|
return
|
|
}
|
|
if err := app.writeJSON(w, http.StatusOK, envelope{"plant_location": assignment}, nil); err != nil {
|
|
app.serverErrorResponse(w, r, err)
|
|
}
|
|
}
|
|
|
|
func (app *application) deletePlantLocationHandler(w http.ResponseWriter, r *http.Request) {
|
|
gardenID, _ := app.readGardenIDParam(r)
|
|
plantID, assignmentID, ok := app.readPlantLocationIDs(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
assignment, err := app.models.PlantLocations.Get(gardenID, assignmentID)
|
|
if err != nil || assignment.PlantID != plantID {
|
|
if err == nil {
|
|
err = storage.ErrRecordNotFound
|
|
}
|
|
app.respondToEntityModelError(w, r, err)
|
|
return
|
|
}
|
|
if err := app.models.PlantLocations.Delete(gardenID, assignmentID); err != nil {
|
|
app.respondToEntityModelError(w, r, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (app *application) readPlantLocationIDs(w http.ResponseWriter, r *http.Request) (int, int, bool) {
|
|
plantID, err := app.readIDParam(r)
|
|
if err != nil {
|
|
app.notFoundResponse(w, r)
|
|
return 0, 0, false
|
|
}
|
|
id, err := app.readPathInt(r, "assignmentID")
|
|
if err != nil {
|
|
app.notFoundResponse(w, r)
|
|
return 0, 0, false
|
|
}
|
|
return plantID, id, true
|
|
}
|
|
|
|
func (app *application) readPathInt(r *http.Request, name string) (int, error) {
|
|
value := httprouter.ParamsFromContext(r.Context()).ByName(name)
|
|
id, err := strconv.Atoi(value)
|
|
if err != nil || id < 1 {
|
|
return 0, errors.New("invalid path parameter")
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
func (app *application) validatePlantLocationForGarden(w http.ResponseWriter, r *http.Request, gardenID int, assignment storage.PlantLocation) bool {
|
|
v := validate.New()
|
|
storage.ValidatePlantLocation(v, assignment)
|
|
if assignment.LocationID > 0 {
|
|
if _, err := app.models.Locations.Get(gardenID, assignment.LocationID); err != nil {
|
|
if errors.Is(err, storage.ErrRecordNotFound) {
|
|
v.AddError("location_id", "must refer to a location in this garden")
|
|
} else {
|
|
app.serverErrorResponse(w, r, err)
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
if !v.Valid() {
|
|
app.failedValidationResponse(w, r, v.Errors)
|
|
return false
|
|
}
|
|
return true
|
|
}
|