@@ -0,0 +1,257 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gardomatic.kleiax.de/internal/platform/validate"
|
||||
"gardomatic.kleiax.de/internal/storage"
|
||||
)
|
||||
|
||||
type plantInput struct {
|
||||
Tags []string `json:"tags"`
|
||||
SpeciesID *int `json:"species_id"`
|
||||
ClearSpeciesID bool `json:"clear_species_id"`
|
||||
Name *string `json:"name"`
|
||||
Notes *string `json:"notes"`
|
||||
ImageData *string `json:"image_data"`
|
||||
ImageID *int `json:"image_id"`
|
||||
AcquiredAt *time.Time `json:"acquired_at"`
|
||||
ClearAcquiredAt bool `json:"clear_acquired_at"`
|
||||
Status *string `json:"status"`
|
||||
RemovedAt *time.Time `json:"removed_at"`
|
||||
Attributes json.RawMessage `json:"attributes"`
|
||||
}
|
||||
|
||||
func (input plantInput) apply(plant *storage.Plant) {
|
||||
if input.SpeciesID != nil {
|
||||
plant.SpeciesID = input.SpeciesID
|
||||
}
|
||||
if input.ClearSpeciesID {
|
||||
plant.SpeciesID = nil
|
||||
}
|
||||
if input.Name != nil {
|
||||
plant.Name = strings.TrimSpace(*input.Name)
|
||||
}
|
||||
if input.Notes != nil {
|
||||
plant.Notes = strings.TrimSpace(*input.Notes)
|
||||
}
|
||||
if input.ImageData != nil {
|
||||
plant.ImageData = *input.ImageData
|
||||
}
|
||||
if input.AcquiredAt != nil {
|
||||
plant.AcquiredAt = input.AcquiredAt
|
||||
}
|
||||
if input.ClearAcquiredAt {
|
||||
plant.AcquiredAt = nil
|
||||
}
|
||||
if input.Status != nil {
|
||||
plant.Status = *input.Status
|
||||
}
|
||||
if input.RemovedAt != nil {
|
||||
plant.RemovedAt = input.RemovedAt
|
||||
}
|
||||
if len(input.Attributes) != 0 {
|
||||
plant.Attributes = input.Attributes
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) createPlantHandler(w http.ResponseWriter, r *http.Request) {
|
||||
gardenID, _ := app.readGardenIDParam(r)
|
||||
var input plantInput
|
||||
if err := app.readJSON(w, r, &input); err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
user, _ := app.contextGetAuthenticatedUser(r)
|
||||
plant := storage.Plant{GardenID: gardenID, Status: "alive", Attributes: json.RawMessage(`{}`), CreatedBy: user.ID, UpdatedBy: user.ID, PlantedBy: user.ID}
|
||||
input.apply(&plant)
|
||||
plant.Tags = storage.NormalizeTags(input.Tags)
|
||||
if !app.validatePlantForGarden(w, r, gardenID, plant) {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
plant.ImageID, err = app.resolveImage(gardenID, plant.ImageData, input.ImageID, user.ID, "plant")
|
||||
if err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
plant.ImageData = ""
|
||||
plant, err = app.models.Plants.Insert(plant)
|
||||
if err != nil {
|
||||
app.respondToEntityModelError(w, r, err)
|
||||
return
|
||||
}
|
||||
plant.Tags, err = app.saveTags(gardenID, storage.TagEntityPlant, plant.ID, plant.Tags)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
headers := make(http.Header)
|
||||
headers.Set("Location", fmt.Sprintf("/v1/gardens/%d/plants/%d", gardenID, plant.ID))
|
||||
if err := app.writeJSON(w, http.StatusCreated, envelope{"plant": plant}, headers); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) listPlantsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
gardenID, _ := app.readGardenIDParam(r)
|
||||
plants, err := app.models.Plants.GetAllForGarden(gardenID)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
user, _ := app.contextGetAuthenticatedUser(r)
|
||||
member, _ := app.contextGetGardenMember(r)
|
||||
visible := plants[:0]
|
||||
for i := range plants {
|
||||
if plants[i].CreatedBy != user.ID && !member.Can(storage.GardenPermissionPlantReadOther) {
|
||||
continue
|
||||
}
|
||||
if plants[i].CreatedBy == user.ID && !member.Can(storage.GardenPermissionPlantReadOwn) {
|
||||
continue
|
||||
}
|
||||
plants[i].Tags, err = app.loadTags(gardenID, storage.TagEntityPlant, plants[i].ID)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
visible = append(visible, plants[i])
|
||||
}
|
||||
plants = visible
|
||||
if err := app.writeJSON(w, http.StatusOK, envelope{"plants": plants}, nil); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) showPlantHandler(w http.ResponseWriter, r *http.Request) {
|
||||
gardenID, _ := app.readGardenIDParam(r)
|
||||
id, err := app.readIDParam(r)
|
||||
if err != nil {
|
||||
app.notFoundResponse(w, r)
|
||||
return
|
||||
}
|
||||
plant, err := app.models.Plants.Get(gardenID, id)
|
||||
if err != nil {
|
||||
app.respondToEntityModelError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !app.authorizeGardenResource(w, r, plant.CreatedBy, storage.GardenPermissionPlantReadOwn, storage.GardenPermissionPlantReadOther) {
|
||||
return
|
||||
}
|
||||
plant.Tags, err = app.loadTags(gardenID, storage.TagEntityPlant, plant.ID)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
if err := app.writeJSON(w, http.StatusOK, envelope{"plant": plant}, nil); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) updatePlantHandler(w http.ResponseWriter, r *http.Request) {
|
||||
gardenID, _ := app.readGardenIDParam(r)
|
||||
id, err := app.readIDParam(r)
|
||||
if err != nil {
|
||||
app.notFoundResponse(w, r)
|
||||
return
|
||||
}
|
||||
plant, err := app.models.Plants.Get(gardenID, id)
|
||||
if err != nil {
|
||||
app.respondToEntityModelError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !app.authorizeGardenResource(w, r, plant.CreatedBy, storage.GardenPermissionPlantUpdateOwn, storage.GardenPermissionPlantUpdateOther) {
|
||||
return
|
||||
}
|
||||
var input plantInput
|
||||
if err := app.readJSON(w, r, &input); err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
previousImageID := plant.ImageID
|
||||
previousImageData := plant.ImageData
|
||||
input.apply(&plant)
|
||||
user, _ := app.contextGetAuthenticatedUser(r)
|
||||
plant.UpdatedBy = user.ID
|
||||
if input.Tags != nil {
|
||||
plant.Tags = storage.NormalizeTags(input.Tags)
|
||||
}
|
||||
if !app.validatePlantForGarden(w, r, gardenID, plant) {
|
||||
return
|
||||
}
|
||||
if input.ImageData != nil && *input.ImageData != previousImageData {
|
||||
plant.ImageID, err = app.resolveImage(gardenID, *input.ImageData, input.ImageID, user.ID, "plant")
|
||||
if err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
plant.ImageData = ""
|
||||
plant, err = app.models.Plants.Update(gardenID, plant)
|
||||
if err != nil {
|
||||
app.respondToEntityModelError(w, r, err)
|
||||
return
|
||||
}
|
||||
if err = app.recordImageAssignment(storage.ImageAssignment{GardenID: gardenID, EntityType: "plant", EntityID: plant.ID, PreviousImageID: previousImageID, ImageID: plant.ImageID, ChangedBy: user.ID}); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
if input.Tags != nil {
|
||||
plant.Tags, err = app.saveTags(gardenID, storage.TagEntityPlant, plant.ID, plant.Tags)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := app.writeJSON(w, http.StatusOK, envelope{"plant": plant}, nil); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) deletePlantHandler(w http.ResponseWriter, r *http.Request) {
|
||||
gardenID, _ := app.readGardenIDParam(r)
|
||||
id, err := app.readIDParam(r)
|
||||
if err != nil {
|
||||
app.notFoundResponse(w, r)
|
||||
return
|
||||
}
|
||||
plant, err := app.models.Plants.Get(gardenID, id)
|
||||
if err != nil {
|
||||
app.respondToEntityModelError(w, r, err)
|
||||
return
|
||||
}
|
||||
if !app.authorizeGardenResource(w, r, plant.CreatedBy, storage.GardenPermissionPlantDeleteOwn, storage.GardenPermissionPlantDeleteOther) {
|
||||
return
|
||||
}
|
||||
if err := app.models.Plants.Delete(gardenID, id); err != nil {
|
||||
app.respondToEntityModelError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (app *application) validatePlantForGarden(w http.ResponseWriter, r *http.Request, gardenID int, plant storage.Plant) bool {
|
||||
v := validate.New()
|
||||
validateImageData(v, plant.ImageData)
|
||||
storage.ValidatePlant(v, plant)
|
||||
if plant.SpeciesID != nil && *plant.SpeciesID > 0 {
|
||||
if _, err := app.models.Species.Get(gardenID, *plant.SpeciesID); err != nil {
|
||||
if errors.Is(err, storage.ErrRecordNotFound) {
|
||||
v.AddError("species_id", "must refer to an available species")
|
||||
} 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