From 102ee5afc3663aad4ca39835eb7ee44960728b6a Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:48:32 -0600 Subject: [PATCH] endpoint for fullpath of an item --- backend/app/api/handlers/v1/v1_ctrl_items.go | 42 ++++++++++ backend/app/api/routes.go | 1 + backend/app/api/static/docs/docs.go | 61 ++++++++++++++ backend/app/api/static/docs/swagger.json | 61 ++++++++++++++ backend/app/api/static/docs/swagger.yaml | 39 +++++++++ .../core/services/reporting/io_row.go | 2 +- backend/internal/data/repo/repo_locations.go | 17 +++- docs/docs/api/openapi-2.0.json | 61 ++++++++++++++ frontend/lib/api/classes/items.ts | 5 ++ frontend/lib/api/types/data-contracts.ts | 11 +++ frontend/pages/item/[id]/index.vue | 83 ++++++++++++------- 11 files changed, 347 insertions(+), 36 deletions(-) diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index 062e378..6a25663 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -93,6 +93,48 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc { } } +// HandleItemFullPath godoc +// +// @Summary Get the full path of an item +// @Tags Items +// @Produce json +// @Param id path string true "Item ID" +// @Success 200 {object} []repo.ItemPath +// @Router /v1/items/{id}/path [GET] +// @Security Bearer +func (ctrl *V1Controller) HandleItemFullPath() errchain.HandlerFunc { + fn := func(r *http.Request, ID uuid.UUID) ([]repo.ItemPath, error) { + auth := services.NewContext(r.Context()) + item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID) + if err != nil { + return nil, err + } + + paths, err := ctrl.repo.Locations.PathForLoc(auth, auth.GID, item.Location.ID) + if err != nil { + return nil, err + } + + if item.Parent != nil { + paths = append(paths, repo.ItemPath{ + Type: repo.ItemTypeItem, + ID: item.Parent.ID, + Name: item.Parent.Name, + }) + } + + paths = append(paths, repo.ItemPath{ + Type: repo.ItemTypeItem, + ID: item.ID, + Name: item.Name, + }) + + return paths, nil + } + + return adapters.CommandID("id", fn, http.StatusOK) +} + // HandleItemsCreate godoc // // @Summary Create Item diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 7ffc313..72371d4 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -122,6 +122,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...)) r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...)) + r.Get(v1Base("/items/{id}/path"), chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...)) r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...)) r.Patch(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...)) r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...)) diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index d5a8b71..7c9a748 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -1017,6 +1017,42 @@ const docTemplate = `{ } } }, + "/v1/items/{id}/path": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Items" + ], + "summary": "Get the full path of an item", + "parameters": [ + { + "type": "string", + "description": "Item ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemPath" + } + } + } + } + } + }, "/v1/labels": { "get": { "security": [ @@ -2168,6 +2204,20 @@ const docTemplate = `{ } } }, + "repo.ItemPath": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/repo.ItemType" + } + } + }, "repo.ItemSummary": { "type": "object", "properties": { @@ -2220,6 +2270,17 @@ const docTemplate = `{ } } }, + "repo.ItemType": { + "type": "string", + "enum": [ + "location", + "item" + ], + "x-enum-varnames": [ + "ItemTypeLocation", + "ItemTypeItem" + ] + }, "repo.ItemUpdate": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 2ff1295..b10c93a 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -1010,6 +1010,42 @@ } } }, + "/v1/items/{id}/path": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Items" + ], + "summary": "Get the full path of an item", + "parameters": [ + { + "type": "string", + "description": "Item ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemPath" + } + } + } + } + } + }, "/v1/labels": { "get": { "security": [ @@ -2161,6 +2197,20 @@ } } }, + "repo.ItemPath": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/repo.ItemType" + } + } + }, "repo.ItemSummary": { "type": "object", "properties": { @@ -2213,6 +2263,17 @@ } } }, + "repo.ItemType": { + "type": "string", + "enum": [ + "location", + "item" + ], + "x-enum-varnames": [ + "ItemTypeLocation", + "ItemTypeItem" + ] + }, "repo.ItemUpdate": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index 509d2cc..dbb31e6 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -206,6 +206,15 @@ definitions: x-nullable: true x-omitempty: true type: object + repo.ItemPath: + properties: + id: + type: string + name: + type: string + type: + $ref: '#/definitions/repo.ItemType' + type: object repo.ItemSummary: properties: archived: @@ -240,6 +249,14 @@ definitions: updatedAt: type: string type: object + repo.ItemType: + enum: + - location + - item + type: string + x-enum-varnames: + - ItemTypeLocation + - ItemTypeItem repo.ItemUpdate: properties: archived: @@ -1264,6 +1281,28 @@ paths: summary: Update Maintenance Entry tags: - Maintenance + /v1/items/{id}/path: + get: + parameters: + - description: Item ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/repo.ItemPath' + type: array + security: + - Bearer: [] + summary: Get the full path of an item + tags: + - Items /v1/items/export: get: responses: diff --git a/backend/internal/core/services/reporting/io_row.go b/backend/internal/core/services/reporting/io_row.go index f097c83..c80e00d 100644 --- a/backend/internal/core/services/reporting/io_row.go +++ b/backend/internal/core/services/reporting/io_row.go @@ -84,7 +84,7 @@ func (csf LocationString) String() string { return strings.Join(csf, " / ") } -func fromPathSlice(s []repo.LocationPath) LocationString { +func fromPathSlice(s []repo.ItemPath) LocationString { v := make(LocationString, len(s)) for i := range s { diff --git a/backend/internal/data/repo/repo_locations.go b/backend/internal/data/repo/repo_locations.go index 081adc4..fd98fd7 100644 --- a/backend/internal/data/repo/repo_locations.go +++ b/backend/internal/data/repo/repo_locations.go @@ -260,12 +260,20 @@ type TreeQuery struct { WithItems bool `json:"withItems" schema:"withItems"` } -type LocationPath struct { +type ItemType string + +const ( + ItemTypeLocation ItemType = "location" + ItemTypeItem ItemType = "item" +) + +type ItemPath struct { + Type ItemType `json:"type"` ID uuid.UUID `json:"id"` Name string `json:"name"` } -func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUID) ([]LocationPath, error) { +func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUID) ([]ItemPath, error) { query := `WITH RECURSIVE location_path AS ( SELECT id, name, location_children FROM locations @@ -288,10 +296,11 @@ func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUI } defer func() { _ = rows.Close() }() - var locations []LocationPath + var locations []ItemPath for rows.Next() { - var location LocationPath + var location ItemPath + location.Type = ItemTypeLocation if err := rows.Scan(&location.ID, &location.Name); err != nil { return nil, err } diff --git a/docs/docs/api/openapi-2.0.json b/docs/docs/api/openapi-2.0.json index 2ff1295..b10c93a 100644 --- a/docs/docs/api/openapi-2.0.json +++ b/docs/docs/api/openapi-2.0.json @@ -1010,6 +1010,42 @@ } } }, + "/v1/items/{id}/path": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Items" + ], + "summary": "Get the full path of an item", + "parameters": [ + { + "type": "string", + "description": "Item ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemPath" + } + } + } + } + } + }, "/v1/labels": { "get": { "security": [ @@ -2161,6 +2197,20 @@ } } }, + "repo.ItemPath": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/repo.ItemType" + } + } + }, "repo.ItemSummary": { "type": "object", "properties": { @@ -2213,6 +2263,17 @@ } } }, + "repo.ItemType": { + "type": "string", + "enum": [ + "location", + "item" + ], + "x-enum-varnames": [ + "ItemTypeLocation", + "ItemTypeItem" + ] + }, "repo.ItemUpdate": { "type": "object", "properties": { diff --git a/frontend/lib/api/classes/items.ts b/frontend/lib/api/classes/items.ts index a8c00e7..ae2399e 100644 --- a/frontend/lib/api/classes/items.ts +++ b/frontend/lib/api/classes/items.ts @@ -5,6 +5,7 @@ import { ItemCreate, ItemOut, ItemPatch, + ItemPath, ItemSummary, ItemUpdate, MaintenanceEntry, @@ -105,6 +106,10 @@ export class ItemsApi extends BaseAPI { this.maintenance = new MaintenanceAPI(http); } + fullpath(id: string) { + return this.http.get({ url: route(`/items/${id}/path`) }); + } + getAll(q: ItemsQuery = {}) { return this.http.get>({ url: route("/items", q) }); } diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts index 67c4f9c..384ffb6 100644 --- a/frontend/lib/api/types/data-contracts.ts +++ b/frontend/lib/api/types/data-contracts.ts @@ -128,6 +128,12 @@ export interface ItemPatch { quantity?: number | null; } +export interface ItemPath { + id: string; + name: string; + type: ItemType; +} + export interface ItemSummary { archived: boolean; createdAt: Date | string; @@ -145,6 +151,11 @@ export interface ItemSummary { updatedAt: Date | string; } +export enum ItemType { + ItemTypeLocation = "location", + ItemTypeItem = "item", +} + export interface ItemUpdate { archived: boolean; assetId: string; diff --git a/frontend/pages/item/[id]/index.vue b/frontend/pages/item/[id]/index.vue index 7855a5f..84d0eb8 100644 --- a/frontend/pages/item/[id]/index.vue +++ b/frontend/pages/item/[id]/index.vue @@ -146,11 +146,6 @@ } const ret: Details = [ - { - name: "Description", - type: "markdown", - text: item.value?.description, - }, { name: "Quantity", text: item.value?.quantity, @@ -405,6 +400,20 @@ ]; }); + const fullpath = computedAsync(async () => { + if (!item.value) { + return []; + } + + const resp = await api.items.fullpath(item.value.id); + if (resp.error) { + toast.error("Failed to load item"); + return []; + } + + return resp.data; + }); + const items = computedAsync(async () => { if (!item.value) { return []; @@ -442,33 +451,45 @@
- - - - {{ item ? item.name : "" }} - - - - - + +
+
+ +
+ +