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