mirror of
https://github.com/hay-kot/homebox.git
synced 2025-08-02 07:40:28 +00:00
endpoint for fullpath of an item
This commit is contained in:
parent
5fe5fac4d4
commit
102ee5afc3
11 changed files with 347 additions and 36 deletions
|
@ -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
|
||||
|
|
|
@ -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...))
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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<ItemPath[]>({ url: route(`/items/${id}/path`) });
|
||||
}
|
||||
|
||||
getAll(q: ItemsQuery = {}) {
|
||||
return this.http.get<PaginationResult<ItemSummary>>({ url: route("/items", q) });
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 @@
|
|||
</dialog>
|
||||
|
||||
<section>
|
||||
<BaseSectionHeader>
|
||||
<Icon name="mdi-package-variant" class="mr-2 -mt-1 text-base-content" />
|
||||
<span class="text-base-content">
|
||||
{{ item ? item.name : "" }}
|
||||
</span>
|
||||
|
||||
<div v-if="item.parent" class="text-sm breadcrumbs pb-0">
|
||||
<ul class="text-base-content/70">
|
||||
<li>
|
||||
<NuxtLink :to="`/item/${item.parent.id}`"> {{ item.parent.name }}</NuxtLink>
|
||||
</li>
|
||||
<li>{{ item.name }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<template #description>
|
||||
<Markdown :source="item.description"> </Markdown>
|
||||
<div class="flex flex-wrap gap-2 mt-3">
|
||||
<NuxtLink v-if="item.location" ref="badge" class="badge p-3" :to="`/location/${item.location.id}`">
|
||||
<Icon name="heroicons-map-pin" class="mr-2 swap-on"></Icon>
|
||||
{{ item.location.name }}
|
||||
</NuxtLink>
|
||||
<template v-if="item.labels && item.labels.length > 0">
|
||||
<LabelChip v-for="label in item.labels" :key="label.id" class="badge-primary" :label="label" />
|
||||
</template>
|
||||
<div class="bg-base-100 rounded p-3">
|
||||
<header class="mb-2">
|
||||
<div class="flex flex-wrap items-end gap-2">
|
||||
<div class="avatar placeholder mb-auto">
|
||||
<div class="bg-neutral-focus text-neutral-content rounded-full w-12">
|
||||
<Icon name="mdi-package-variant" class="h-7 w-7" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="fullpath && fullpath.length > 0" class="text-sm breadcrumbs pt-0 pb-0">
|
||||
<ul class="text-base-content/70">
|
||||
<li v-for="part in fullpath" :key="part.id">
|
||||
<NuxtLink :to="`/${part.type}/${part.id}`"> {{ part.name }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-2xl pb-1">
|
||||
{{ item ? item.name : "" }}
|
||||
</h1>
|
||||
<div class="flex gap-1 flex-wrap text-xs">
|
||||
<div>
|
||||
Created
|
||||
<DateTime :date="item?.createdAt" />
|
||||
</div>
|
||||
-
|
||||
<div>
|
||||
Updated
|
||||
<DateTime :date="item?.updatedAt" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BaseSectionHeader>
|
||||
</header>
|
||||
<div class="divider my-0 mb-1"></div>
|
||||
<div class="p-1 prose max-w-[100%]">
|
||||
<Markdown v-if="item && item.description" class="text-base" :source="item.description"> </Markdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between mb-6 mt-3">
|
||||
<div class="btn-group">
|
||||
<NuxtLink
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue