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 }