From b2c8c2915f8c9ab7ebdbf5f83bc998a0c2b143ce Mon Sep 17 00:00:00 2001 From: Brett Bender Date: Sun, 21 Jan 2024 01:02:40 -0600 Subject: [PATCH] feat: better API returns, asset update --- internal/api/api.go | 1 + internal/api/assets.go | 41 ++++++++++++++++++++++++++++++++++- internal/api/shelves.go | 28 ++++++++++++++++++++++-- internal/storage/datastore.go | 24 +++++++++++++++----- internal/types/api.go | 5 +++++ internal/types/buildings.go | 2 +- internal/types/shelves.go | 2 +- 7 files changed, 93 insertions(+), 10 deletions(-) diff --git a/internal/api/api.go b/internal/api/api.go index 8f1e3a4..78a605a 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -67,6 +67,7 @@ func (s *APIServer) handleIndex(w http.ResponseWriter, r *http.Request) error { func makeHandler(f APIFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := f(w, r); err != nil { + log.Printf("Err: %v", err) render.Render(w, r, errBadRequest(err)) } } diff --git a/internal/api/assets.go b/internal/api/assets.go index 6566ce6..74535c6 100644 --- a/internal/api/assets.go +++ b/internal/api/assets.go @@ -19,6 +19,7 @@ func (s *APIServer) setupAssetRoutes() func(r chi.Router) { r.Use(s.AssetCtx) r.Get("/", makeHandler(s.getAsset)) r.Delete("/", makeHandler(s.deleteAsset)) + r.Put("/", makeHandler(s.updateAsset)) }) } } @@ -42,7 +43,7 @@ func (s *APIServer) AssetCtx(next http.Handler) http.Handler { } func (s *APIServer) getAssets(w http.ResponseWriter, r *http.Request) error { - assets, err := s.db.GetAssets(0, 50) // TODO: Proper Pagination + assets, err := s.db.GetAssets(0, 1000) // TODO: Proper Pagination if err != nil { return err } @@ -120,3 +121,41 @@ func (s *APIServer) createAsset(w http.ResponseWriter, r *http.Request) error { Asset: asset, }) } + +func (s *APIServer) updateAsset(w http.ResponseWriter, r *http.Request) error { + ctx := r.Context() + eAsset, ok := ctx.Value("asset").(*types.Asset) + if !ok { + return render.Render(w, r, errUnprocessable) + } + + data := &types.CreateAssetRequest{} + if err := render.Bind(r, data); err != nil { + return render.Render(w, r, errBadRequest(err)) + } + + asset := &types.Asset{ + ID: eAsset.ID, + Name: data.Name, + Quantity: data.Quantity, + Length: data.Length, + Manufacturer: data.Manufacturer, + ModelName: data.ModelName, + Price: data.Price, + Comments: data.Comments, + ShelfLocationID: data.ShelfLocationID, + CategoryID: data.CategoryID, + } + + err := s.db.UpdateAsset(eAsset.ID, asset) + if err != nil { + return err + } + + return render.Render(w, r, &types.AssetResponse{ + Response: &types.Response{ + HTTPStatusCode: http.StatusOK, + }, + Asset: asset, + }) +} diff --git a/internal/api/shelves.go b/internal/api/shelves.go index cb06dfd..357de6c 100644 --- a/internal/api/shelves.go +++ b/internal/api/shelves.go @@ -2,6 +2,7 @@ package api import ( "context" + "log" "net/http" "strconv" @@ -15,24 +16,27 @@ func (s *APIServer) setupShelfRoutes() func(chi.Router) { r.Get("/", makeHandler(s.getShelves)) r.Post("/", makeHandler(s.createShelf)) - r.Route("/{shelfID}", func(r chi.Router) { + r.Route("/{shelfId}", func(r chi.Router) { r.Use(s.ShelfCtx) r.Get("/", makeHandler(s.getShelf)) r.Delete("/", makeHandler(s.deleteShelf)) + r.Get("/count", makeHandler(s.getShelfAssetCount)) }) } } func (s *APIServer) ShelfCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - shelfIdStr := chi.URLParam(r, "shelfID") + shelfIdStr := chi.URLParam(r, "shelfId") shelfId, err := strconv.ParseUint(shelfIdStr, 10, 64) if err != nil { + log.Println("Test") render.Render(w, r, errNotFound) return } shelf, err := s.db.GetShelfByID(shelfId) if err != nil { + log.Printf("Test 2: %v\n", err) render.Render(w, r, errNotFound) return } @@ -120,3 +124,23 @@ func (s *APIServer) createShelf(w http.ResponseWriter, r *http.Request) error { ShelfLocation: shelf, }) } + +func (s *APIServer) getShelfAssetCount(w http.ResponseWriter, r *http.Request) error { + ctx := r.Context() + shelf, ok := ctx.Value("shelf").(*types.ShelfLocation) + if !ok { + return render.Render(w, r, errUnprocessable) + } + + count, err := s.db.AssetCountInLocation(shelf.ID) + if err != nil { + return err + } + + return render.Render(w, r, &types.CountResponse{ + Response: &types.Response{ + HTTPStatusCode: http.StatusOK, + }, + Count: count, + }) +} diff --git a/internal/storage/datastore.go b/internal/storage/datastore.go index 3bbf35e..d1b65d8 100644 --- a/internal/storage/datastore.go +++ b/internal/storage/datastore.go @@ -7,6 +7,7 @@ import ( "git.brettb.xyz/goinv/server/internal/types" "gorm.io/driver/postgres" "gorm.io/gorm" + "gorm.io/gorm/clause" "gorm.io/gorm/logger" ) @@ -47,6 +48,11 @@ func (s *DataStore) CreateAsset(asset *types.Asset) error { return result.Error } +func (s *DataStore) UpdateAsset(id uint64, update *types.Asset) error { + result := s.db.Model(&types.Asset{}).Where("id = ?", id).Select("*").Updates(update) + return result.Error +} + func (s *DataStore) CreateBuilding(building *types.Building) error { result := s.db.Create(building) return result.Error @@ -64,7 +70,7 @@ func (s *DataStore) CreateShelfLocation(shelf *types.ShelfLocation) error { func (s *DataStore) GetAssetByID(id uint64) (*types.Asset, error) { var result types.Asset - tx := s.db.Model(&types.Asset{}).Where("id = ?", id).First(&result) + tx := s.db.Model(&types.Asset{}).Where("id = ?", id).Preload(clause.Associations).First(&result) if tx.Error != nil { return nil, fmt.Errorf("asset %d not found", id) } @@ -88,6 +94,14 @@ func (s *DataStore) TotalAssets() (int64, error) { return count, nil } +func (s *DataStore) AssetCountInLocation(shelfId uint64) (int64, error) { + var count int64 + if tx := s.db.Model(&types.Asset{}).Where("shelf_location_id = ?", shelfId).Find(&types.Asset{}).Count(&count); tx.Error != nil { + return 0, tx.Error + } + return count, nil +} + func (s *DataStore) DeleteAssetByID(id uint64) (bool, error) { tx := s.db.Delete(&types.Asset{}, id) if tx.Error != nil { @@ -98,16 +112,16 @@ func (s *DataStore) DeleteAssetByID(id uint64) (bool, error) { func (s *DataStore) GetShelfByID(id uint64) (*types.ShelfLocation, error) { var result types.ShelfLocation - tx := s.db.Model(&types.ShelfLocation{}).Where("id = ?", id).First(&result) + tx := s.db.Joins("Building").Where(&types.ShelfLocation{ID: id}).First(&result) if tx.Error != nil { - return nil, fmt.Errorf("shelf %d not found", id) + return nil, fmt.Errorf("shelf %d not found: %v", id, tx.Error) } return &result, nil } -func (s *DataStore) GetShelves(offset, limit int) ([]*types.ShelfLocation, error) { +func (s *DataStore) GetShelves(offset, limit uint64) ([]*types.ShelfLocation, error) { var shelves []*types.ShelfLocation - s.db.Offset(offset).Limit(limit).Find(&shelves) + s.db.Joins("Building").Order("id asc").Offset(int(offset)).Limit(int(limit)).Find(&shelves) if len(shelves) == 0 { return nil, fmt.Errorf("no shelves found") } diff --git a/internal/types/api.go b/internal/types/api.go index 4ea8d73..7e1baf7 100644 --- a/internal/types/api.go +++ b/internal/types/api.go @@ -26,6 +26,11 @@ type IndexResponse struct { Version APIVersion `json:"version"` } +type CountResponse struct { + *Response + Count int64 `json:"count"` +} + type APIError struct { *Response Err error `json:"-"` diff --git a/internal/types/buildings.go b/internal/types/buildings.go index f22c35d..bd7e00f 100644 --- a/internal/types/buildings.go +++ b/internal/types/buildings.go @@ -14,7 +14,7 @@ import ( type Building struct { ID uint64 `gorm:"primarykey" json:"id"` Name string `json:"name"` - ShelfLocations []ShelfLocation `json:"shelves"` + ShelfLocations []ShelfLocation `json:"shelves,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"` diff --git a/internal/types/shelves.go b/internal/types/shelves.go index 586e9c5..d057e3a 100644 --- a/internal/types/shelves.go +++ b/internal/types/shelves.go @@ -17,7 +17,7 @@ type ShelfLocation struct { RoomNumber string `json:"room_number,omitempty"` Description string `json:"description,omitempty"` BuildingID uint64 `json:"-"` - Building Building `json:"building"` + Building Building `json:"building",omitempty` Assets []Asset `json:"assets,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"`