From ba739a02da352c250d7d20e3df444e5ba917e01e Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 5 Dec 2022 17:51:40 -0900 Subject: [PATCH] implement repository layer --- backend/internal/data/repo/map_helpers.go | 19 ++- .../data/repo/repo_maintenance_entry.go | 118 ++++++++++++++++++ .../data/repo/repo_maintenance_entry_test.go | 66 ++++++++++ backend/internal/data/repo/repos_all.go | 2 + 4 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 backend/internal/data/repo/repo_maintenance_entry.go create mode 100644 backend/internal/data/repo/repo_maintenance_entry_test.go diff --git a/backend/internal/data/repo/map_helpers.go b/backend/internal/data/repo/map_helpers.go index a9c0bca..9404cb0 100644 --- a/backend/internal/data/repo/map_helpers.go +++ b/backend/internal/data/repo/map_helpers.go @@ -16,17 +16,16 @@ func mapTErrFunc[T any, Y any](fn func(T) Y) func(T, error) (Y, error) { } } -// TODO: Future Usage -// func mapEachFunc[T any, Y any](fn func(T) Y) func([]T) []Y { -// return func(items []T) []Y { -// result := make([]Y, len(items)) -// for i, item := range items { -// result[i] = fn(item) -// } +func mapTEachFunc[T any, Y any](fn func(T) Y) func([]T) []Y { + return func(items []T) []Y { + result := make([]Y, len(items)) + for i, item := range items { + result[i] = fn(item) + } -// return result -// } -// } + return result + } +} func mapTEachErrFunc[T any, Y any](fn func(T) Y) func([]T, error) ([]Y, error) { return func(items []T, err error) ([]Y, error) { diff --git a/backend/internal/data/repo/repo_maintenance_entry.go b/backend/internal/data/repo/repo_maintenance_entry.go new file mode 100644 index 0000000..70f554e --- /dev/null +++ b/backend/internal/data/repo/repo_maintenance_entry.go @@ -0,0 +1,118 @@ +package repo + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent" + "github.com/hay-kot/homebox/backend/internal/data/ent/maintenanceentry" +) + +// MaintenanceEntryRepository is a repository for maintenance entries that are +// associated with an item in the database. An entry represents a maintenance event +// that has been performed on an item. +type MaintenanceEntryRepository struct { + db *ent.Client +} +type ( + MaintenanceEntryCreate struct { + ItemID uuid.UUID `json:"itemId"` + Date time.Time `json:"date"` + Name string `json:"name"` + Description string `json:"description"` + Cost float64 `json:"cost"` + } + + MaintenanceEntry struct { + ID uuid.UUID `json:"id"` + Date time.Time `json:"date"` + Name string `json:"name"` + Description string `json:"description"` + Cost float64 `json:"cost"` + } + + MaintenanceLog struct { + ItemID uuid.UUID `json:"itemId"` + CostAverage float64 `json:"costAverage"` + CostTotal float64 `json:"costTotal"` + Entries []MaintenanceEntry + } +) + +var ( + mapMaintenanceEntryErr = mapTErrFunc(mapMaintenanceEntry) + mapEachMaintenanceEntry = mapTEachFunc(mapMaintenanceEntry) +) + +func mapMaintenanceEntry(entry *ent.MaintenanceEntry) MaintenanceEntry { + return MaintenanceEntry{ + ID: entry.ID, + Date: entry.Date, + Name: entry.Name, + Description: entry.Description, + Cost: entry.Cost, + } +} + +func (r *MaintenanceEntryRepository) Create(ctx context.Context, input MaintenanceEntryCreate) (MaintenanceEntry, error) { + item, err := r.db.MaintenanceEntry.Create(). + SetItemID(input.ItemID). + SetDate(input.Date). + SetName(input.Name). + SetDescription(input.Description). + SetCost(input.Cost). + Save(ctx) + + return mapMaintenanceEntryErr(item, err) +} + +func (r *MaintenanceEntryRepository) GetLog(ctx context.Context, itemID uuid.UUID) (MaintenanceLog, error) { + log := MaintenanceLog{ + ItemID: itemID, + } + + entries, err := r.db.MaintenanceEntry.Query(). + Where(maintenanceentry.ItemID(itemID)). + All(ctx) + + if err != nil { + return MaintenanceLog{}, err + } + + log.Entries = mapEachMaintenanceEntry(entries) + + var maybeTotal *float64 + var maybeAverage *float64 + + q := ` +SELECT + SUM(cost_total) AS total_of_totals, + AVG(cost_total) AS avg_of_averages +FROM + ( + SELECT + strftime('%m-%Y', date) AS my, + SUM(cost) AS cost_total + FROM + maintenance_entries + WHERE + item_id = ? + GROUP BY + my + )` + + row := r.db.Sql().QueryRowContext(ctx, q, itemID) + err = row.Scan(&maybeTotal, &maybeAverage) + if err != nil { + return MaintenanceLog{}, err + } + + log.CostAverage = orDefault(maybeAverage, 0) + log.CostTotal = orDefault(maybeTotal, 0) + return log, nil +} + +func (r *MaintenanceEntryRepository) Delete(ctx context.Context, ID uuid.UUID) error { + return r.db.MaintenanceEntry.DeleteOneID(ID).Exec(ctx) +} diff --git a/backend/internal/data/repo/repo_maintenance_entry_test.go b/backend/internal/data/repo/repo_maintenance_entry_test.go new file mode 100644 index 0000000..a1ad51b --- /dev/null +++ b/backend/internal/data/repo/repo_maintenance_entry_test.go @@ -0,0 +1,66 @@ +package repo + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestMaintenanceEntryRepository_GetLog(t *testing.T) { + item := useItems(t, 1)[0] + + // Create 10 maintenance entries for the item + created := make([]MaintenanceEntryCreate, 10) + + lastMonth := time.Now().AddDate(0, -1, 0) + thisMonth := time.Now() + + for i := 0; i < 10; i++ { + dt := lastMonth + if i%2 == 0 { + dt = thisMonth + } + + created[i] = MaintenanceEntryCreate{ + ItemID: item.ID, + Date: dt, + Name: "Maintenance", + Description: "Maintenance description", + Cost: 10, + } + } + + for _, entry := range created { + _, err := tRepos.MaintEntry.Create(context.Background(), entry) + if err != nil { + t.Fatalf("failed to create maintenance entry: %v", err) + } + } + + // Get the log for the item + log, err := tRepos.MaintEntry.GetLog(context.Background(), item.ID) + + if err != nil { + t.Fatalf("failed to get maintenance log: %v", err) + } + + assert.Equal(t, item.ID, log.ItemID) + assert.Equal(t, 10, len(log.Entries)) + + // Calculate the average cost + var total float64 + + for _, entry := range log.Entries { + total += entry.Cost + } + + assert.Equal(t, total, log.CostTotal, "total cost should be equal to the sum of all entries") + assert.Equal(t, total/2, log.CostAverage, "average cost should be the average of the two months") + + for _, entry := range log.Entries { + err := tRepos.MaintEntry.Delete(context.Background(), entry.ID) + assert.NoError(t, err) + } +} diff --git a/backend/internal/data/repo/repos_all.go b/backend/internal/data/repo/repos_all.go index 5ca4d3e..40748cb 100644 --- a/backend/internal/data/repo/repos_all.go +++ b/backend/internal/data/repo/repos_all.go @@ -12,6 +12,7 @@ type AllRepos struct { Items *ItemsRepository Docs *DocumentRepository Attachments *AttachmentRepo + MaintEntry *MaintenanceEntryRepository } func New(db *ent.Client, root string) *AllRepos { @@ -24,5 +25,6 @@ func New(db *ent.Client, root string) *AllRepos { Items: &ItemsRepository{db}, Docs: &DocumentRepository{db, root}, Attachments: &AttachmentRepo{db}, + MaintEntry: &MaintenanceEntryRepository{db}, } }