homebox/backend/internal/services/service_items.go
Hayden 95ab14b866
feat: items-editor (#5)
* format readme

* update logo

* format html

* add logo to docs

* repository for document and document tokens

* add attachments type and repository

* autogenerate types via scripts

* use autogenerated types

* attachment type updates

* add insured and quantity fields for items

* implement HasID interface for entities

* implement label updates for items

* implement service update method

* WIP item update client side actions

* check err on attachment

* finish types for basic items editor

* remove unused var

* house keeping
2022-09-12 14:47:27 -08:00

271 lines
6.3 KiB
Go

package services
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"github.com/google/uuid"
"github.com/hay-kot/content/backend/ent/attachment"
"github.com/hay-kot/content/backend/internal/repo"
"github.com/hay-kot/content/backend/internal/services/mappers"
"github.com/hay-kot/content/backend/internal/types"
"github.com/rs/zerolog/log"
)
type ItemService struct {
repo *repo.AllRepos
// filepath is the root of the storage location that will be used to store all files from.
filepath string
}
func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (*types.ItemOut, error) {
result, err := svc.repo.Items.GetOne(ctx, id)
if err != nil {
return nil, err
}
if result.Edges.Group.ID != gid {
return nil, ErrNotOwner
}
return mappers.ToItemOut(result), nil
}
func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]*types.ItemSummary, error) {
items, err := svc.repo.Items.GetAll(ctx, gid)
if err != nil {
return nil, err
}
itemsOut := make([]*types.ItemSummary, len(items))
for i, item := range items {
itemsOut[i] = mappers.ToItemSummary(item)
}
return itemsOut, nil
}
func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data types.ItemCreate) (*types.ItemOut, error) {
item, err := svc.repo.Items.Create(ctx, gid, data)
if err != nil {
return nil, err
}
return mappers.ToItemOut(item), nil
}
func (svc *ItemService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error {
item, err := svc.repo.Items.GetOne(ctx, id)
if err != nil {
return err
}
if item.Edges.Group.ID != gid {
return ErrNotOwner
}
err = svc.repo.Items.Delete(ctx, id)
if err != nil {
return err
}
return nil
}
func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data types.ItemUpdate) (*types.ItemOut, error) {
item, err := svc.repo.Items.GetOne(ctx, data.ID)
if err != nil {
return nil, err
}
if item.Edges.Group.ID != gid {
return nil, ErrNotOwner
}
item, err = svc.repo.Items.Update(ctx, data)
if err != nil {
return nil, err
}
return mappers.ToItemOut(item), nil
}
func (svc *ItemService) attachmentPath(gid, itemId uuid.UUID, filename string) string {
return filepath.Join(svc.filepath, gid.String(), itemId.String(), filename)
}
// AddAttachment adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
// Table and Items table. The file provided via the reader is stored on the file system based on the provided
// relative path during construction of the service.
func (svc *ItemService) AddAttachment(ctx context.Context, gid, itemId uuid.UUID, filename string, file io.Reader) (*types.ItemOut, error) {
// Get the Item
item, err := svc.repo.Items.GetOne(ctx, itemId)
if err != nil {
return nil, err
}
if item.Edges.Group.ID != gid {
return nil, ErrNotOwner
}
// Create the document
doc, err := svc.repo.Docs.Create(ctx, gid, types.DocumentCreate{
Title: filename,
Path: svc.attachmentPath(gid, itemId, filename),
})
if err != nil {
return nil, err
}
// Create the attachment
_, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachment.TypeAttachment)
if err != nil {
return nil, err
}
// Read the contents and write them to a file on the file system
err = os.MkdirAll(filepath.Dir(doc.Path), os.ModePerm)
if err != nil {
return nil, err
}
f, err := os.Create(doc.Path)
if err != nil {
log.Err(err).Msg("failed to create file")
return nil, err
}
_, err = io.Copy(f, file)
if err != nil {
return nil, err
}
return svc.GetOne(ctx, gid, itemId)
}
func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]string) error {
loaded := []csvRow{}
// Skip first row
for _, row := range data[1:] {
// Skip empty rows
if len(row) == 0 {
continue
}
if len(row) != 14 {
return ErrInvalidCsv
}
r := newCsvRow(row)
loaded = append(loaded, r)
}
// Bootstrap the locations and labels so we can reuse the created IDs for the items
locations := map[string]uuid.UUID{}
existingLocation, err := svc.repo.Locations.GetAll(ctx, gid)
if err != nil {
return err
}
for _, loc := range existingLocation {
locations[loc.Name] = loc.ID
}
labels := map[string]uuid.UUID{}
existingLabels, err := svc.repo.Labels.GetAll(ctx, gid)
if err != nil {
return err
}
for _, label := range existingLabels {
labels[label.Name] = label.ID
}
for _, row := range loaded {
// Locations
if _, ok := locations[row.Location]; ok {
continue
}
fmt.Println("Creating Location: ", row.Location)
result, err := svc.repo.Locations.Create(ctx, gid, types.LocationCreate{
Name: row.Location,
Description: "",
})
if err != nil {
return err
}
locations[row.Location] = result.ID
// Labels
for _, label := range row.getLabels() {
if _, ok := labels[label]; ok {
continue
}
result, err := svc.repo.Labels.Create(ctx, gid, types.LabelCreate{
Name: label,
Description: "",
})
if err != nil {
return err
}
labels[label] = result.ID
}
}
// Create the items
for _, row := range loaded {
locationID := locations[row.Location]
labelIDs := []uuid.UUID{}
for _, label := range row.getLabels() {
labelIDs = append(labelIDs, labels[label])
}
log.Info().
Str("name", row.Name).
Str("location", row.Location).
Strs("labels", row.getLabels()).
Str("locationId", locationID.String()).
Msgf("Creating Item: %s", row.Name)
result, err := svc.repo.Items.Create(ctx, gid, types.ItemCreate{
Name: row.Name,
Description: row.Description,
LabelIDs: labelIDs,
LocationID: locationID,
})
if err != nil {
return err
}
// Update the item with the rest of the data
_, err = svc.repo.Items.Update(ctx, types.ItemUpdate{
ID: result.ID,
Name: result.Name,
LocationID: locationID,
LabelIDs: labelIDs,
Description: result.Description,
SerialNumber: row.SerialNumber,
ModelNumber: row.ModelNumber,
Manufacturer: row.Manufacturer,
Notes: row.Notes,
PurchaseFrom: row.PurchaseFrom,
PurchasePrice: row.parsedPurchasedPrice(),
PurchaseTime: row.parsedPurchasedAt(),
SoldTo: row.SoldTo,
SoldPrice: row.parsedSoldPrice(),
SoldTime: row.parsedSoldAt(),
})
if err != nil {
return err
}
}
return nil
}