mirror of
https://github.com/hay-kot/homebox.git
synced 2025-07-07 02:58:35 +00:00
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
This commit is contained in:
parent
fbc364dcd2
commit
95ab14b866
125 changed files with 15626 additions and 1791 deletions
|
@ -16,6 +16,9 @@ func NewServices(repos *repo.AllRepos) *AllServices {
|
|||
Admin: &AdminService{repos},
|
||||
Location: &LocationService{repos},
|
||||
Labels: &LabelService{repos},
|
||||
Items: &ItemService{repos},
|
||||
Items: &ItemService{
|
||||
repo: repos,
|
||||
filepath: "/tmp/content",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ var (
|
|||
tRepos *repo.AllRepos
|
||||
tUser *ent.User
|
||||
tGroup *ent.Group
|
||||
tSvc *AllServices
|
||||
)
|
||||
|
||||
func bootstrap() {
|
||||
|
@ -36,10 +37,10 @@ func bootstrap() {
|
|||
}
|
||||
|
||||
tUser, err = tRepos.Users.Create(ctx, types.UserCreate{
|
||||
Name: fk.RandomString(10),
|
||||
Email: fk.RandomEmail(),
|
||||
Password: fk.RandomString(10),
|
||||
IsSuperuser: fk.RandomBool(),
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: fk.Str(10),
|
||||
IsSuperuser: fk.Bool(),
|
||||
GroupID: tGroup.ID,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -62,6 +63,7 @@ func TestMain(m *testing.M) {
|
|||
|
||||
tClient = client
|
||||
tRepos = repo.EntAllRepos(tClient)
|
||||
tSvc = NewServices(tRepos)
|
||||
defer client.Close()
|
||||
|
||||
bootstrap()
|
||||
|
|
|
@ -5,6 +5,19 @@ import (
|
|||
"github.com/hay-kot/content/backend/internal/types"
|
||||
)
|
||||
|
||||
func ToItemAttachment(attachment *ent.Attachment) *types.ItemAttachment {
|
||||
return &types.ItemAttachment{
|
||||
ID: attachment.ID,
|
||||
CreatedAt: attachment.CreatedAt,
|
||||
UpdatedAt: attachment.UpdatedAt,
|
||||
Document: types.DocumentOut{
|
||||
ID: attachment.Edges.Document.ID,
|
||||
Title: attachment.Edges.Document.Title,
|
||||
Path: attachment.Edges.Document.Path,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ToItemSummary(item *ent.Item) *types.ItemSummary {
|
||||
var location *types.LocationSummary
|
||||
if item.Edges.Location != nil {
|
||||
|
@ -23,6 +36,14 @@ func ToItemSummary(item *ent.Item) *types.ItemSummary {
|
|||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
|
||||
Quantity: item.Quantity,
|
||||
Insured: item.Insured,
|
||||
|
||||
// Warranty
|
||||
LifetimeWarranty: item.LifetimeWarranty,
|
||||
WarrantyExpires: item.WarrantyExpires,
|
||||
WarrantyDetails: item.WarrantyDetails,
|
||||
|
||||
// Edges
|
||||
Location: location,
|
||||
Labels: labels,
|
||||
|
@ -53,8 +74,14 @@ func ToItemSummaryErr(item *ent.Item, err error) (*types.ItemSummary, error) {
|
|||
}
|
||||
|
||||
func ToItemOut(item *ent.Item) *types.ItemOut {
|
||||
var attachments []*types.ItemAttachment
|
||||
if item.Edges.Attachments != nil {
|
||||
attachments = MapEach(item.Edges.Attachments, ToItemAttachment)
|
||||
}
|
||||
|
||||
return &types.ItemOut{
|
||||
ItemSummary: *ToItemSummary(item),
|
||||
Attachments: attachments,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,12 @@ 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"
|
||||
|
@ -13,6 +17,9 @@ import (
|
|||
|
||||
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) {
|
||||
|
@ -41,6 +48,7 @@ func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]*types.Ite
|
|||
|
||||
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 {
|
||||
|
@ -49,6 +57,7 @@ func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data types.It
|
|||
|
||||
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 {
|
||||
|
@ -66,8 +75,76 @@ func (svc *ItemService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID)
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data types.ItemUpdate) (*types.ItemOut, error) {
|
||||
panic("implement me")
|
||||
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 {
|
||||
|
|
|
@ -2,13 +2,16 @@ package services
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/content/backend/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
|
||||
func TestItemService_CsvImport(t *testing.T) {
|
||||
data := loadcsv()
|
||||
svc := &ItemService{
|
||||
|
@ -55,6 +58,14 @@ func TestItemService_CsvImport(t *testing.T) {
|
|||
labelNames = append(labelNames, label.Name)
|
||||
}
|
||||
|
||||
ids := []uuid.UUID{}
|
||||
t.Cleanup((func() {
|
||||
for _, id := range ids {
|
||||
err := svc.repo.Items.Delete(context.Background(), id)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
|
||||
for _, item := range items {
|
||||
assert.Contains(t, locNames, item.Location.Name)
|
||||
for _, label := range item.Labels {
|
||||
|
@ -79,6 +90,55 @@ func TestItemService_CsvImport(t *testing.T) {
|
|||
assert.Equal(t, csvRow.parsedSoldPrice(), item.SoldPrice)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestItemService_AddAttachment(t *testing.T) {
|
||||
temp := os.TempDir()
|
||||
|
||||
svc := &ItemService{
|
||||
repo: tRepos,
|
||||
filepath: temp,
|
||||
}
|
||||
|
||||
loc, err := tSvc.Location.Create(context.Background(), tGroup.ID, types.LocationCreate{
|
||||
Description: "test",
|
||||
Name: "test",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, loc)
|
||||
|
||||
itmC := types.ItemCreate{
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(10),
|
||||
LocationID: loc.ID,
|
||||
}
|
||||
|
||||
itm, err := svc.Create(context.Background(), tGroup.ID, itmC)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, itm)
|
||||
t.Cleanup(func() {
|
||||
err := svc.repo.Items.Delete(context.Background(), itm.ID)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
contents := fk.Str(1000)
|
||||
reader := strings.NewReader(contents)
|
||||
|
||||
// Setup
|
||||
afterAttachment, err := svc.AddAttachment(context.Background(), tGroup.ID, itm.ID, "testfile.txt", reader)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, afterAttachment)
|
||||
|
||||
// Check that the file exists
|
||||
storedPath := afterAttachment.Attachments[0].Document.Path
|
||||
|
||||
// {root}/{group}/{item}/{attachment}
|
||||
assert.Equal(t, path.Join(temp, tGroup.ID.String(), itm.ID.String(), "testfile.txt"), storedPath)
|
||||
|
||||
// Check that the file contents are correct
|
||||
bts, err := os.ReadFile(storedPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, contents, string(bts))
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue