mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 21:33:02 +01:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab34791737 | ||
|
|
d03e60d580 | ||
|
|
6fcf9965bb | ||
|
|
d53dcd37e6 | ||
|
|
f3531cacb3 | ||
|
|
10cdca94dc | ||
|
|
0b2b7bc4fd | ||
|
|
105f63487b | ||
|
|
f3c745e42e | ||
|
|
f3e7d7a19b | ||
|
|
af1ab9d1af | ||
|
|
69e5a877c0 | ||
|
|
5c0d161eb4 | ||
|
|
91cd0d1bca | ||
|
|
b12011f4a6 | ||
|
|
0223fbbb3f | ||
|
|
a709d38946 | ||
|
|
bd2eb8b5ac | ||
|
|
5a015b9581 | ||
|
|
552cb0bf53 | ||
|
|
a93f4ff1ad | ||
|
|
3d283c2d81 | ||
|
|
ea1d92207a | ||
|
|
bcd826ed4f | ||
|
|
499bb90b09 | ||
|
|
4f00822849 | ||
| ff28175838 | |||
|
|
0f482aebad | ||
|
|
76fe0d3522 | ||
|
|
4995e04cf0 | ||
|
|
76123e00d6 | ||
|
|
0b7de9557d | ||
|
|
0f25983278 | ||
|
|
40a093656b | ||
|
|
8b8a96f93b | ||
|
|
7b9c3d52cd | ||
|
|
a0198fb66f | ||
|
|
04eb136ab0 | ||
|
|
16f3fb19e8 | ||
|
|
6a1ffd7700 | ||
|
|
24dc182c0e | ||
| 9ed618d45e | |||
| e79905b608 | |||
| e929c38e37 | |||
| e406bb2d04 | |||
|
|
30abdd4d36 | ||
|
|
8a57ca41bf | ||
|
|
b57efa02ff | ||
|
|
d7bf64742e | ||
|
|
7e150aa8d7 | ||
|
|
4d916a69af | ||
|
|
7096616414 | ||
|
|
2292d72802 | ||
|
|
a8d21f0465 | ||
|
|
53d542413b | ||
|
|
aaeac8ca9d | ||
|
|
a780c6fac4 |
16
.github/pull_request_template.md
vendored
16
.github/pull_request_template.md
vendored
@@ -55,18 +55,4 @@ _(fill-in or delete this section)_
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Describe how you tested this change.
|
Describe how you tested this change.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Release Notes
|
|
||||||
|
|
||||||
_(REQUIRED)_
|
|
||||||
<!--
|
|
||||||
If this PR makes user facing changes, please describe them here. This
|
|
||||||
description will be copied into the release notes/changelog, whenever the
|
|
||||||
next version is released. Keep this section short, and focus on high level
|
|
||||||
changes.
|
|
||||||
Put your text between the block. To omit notes, use NONE within the block.
|
|
||||||
-->
|
|
||||||
|
|
||||||
```release-note
|
|
||||||
```
|
|
||||||
12
.github/workflows/binaries-publish.yaml
vendored
12
.github/workflows/binaries-publish.yaml
vendored
@@ -5,13 +5,13 @@ on:
|
|||||||
tags: [ 'v*.*.*' ]
|
tags: [ 'v*.*.*' ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backend-tests:
|
# backend-tests:
|
||||||
name: "Backend Server Tests"
|
# name: "Backend Server Tests"
|
||||||
uses: sysadminsmedia/homebox/.github/workflows/partial-backend.yaml@main
|
# uses: sysadminsmedia/homebox/.github/workflows/partial-backend.yaml@main
|
||||||
|
|
||||||
frontend-tests:
|
# frontend-tests:
|
||||||
name: "Frontend and End-to-End Tests"
|
# name: "Frontend and End-to-End Tests"
|
||||||
uses: sysadminsmedia/homebox/.github/workflows/partial-frontend.yaml@main
|
# uses: sysadminsmedia/homebox/.github/workflows/partial-frontend.yaml@main
|
||||||
|
|
||||||
goreleaser:
|
goreleaser:
|
||||||
name: goreleaser
|
name: goreleaser
|
||||||
|
|||||||
12
.github/workflows/docker-publish-rootless.yaml
vendored
12
.github/workflows/docker-publish-rootless.yaml
vendored
@@ -8,6 +8,10 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'backend/**'
|
- 'backend/**'
|
||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'Dockerfile.rootless'
|
||||||
|
- '.dockerignore'
|
||||||
|
- '.github/workflows'
|
||||||
# Publish semver tags as releases.
|
# Publish semver tags as releases.
|
||||||
tags: [ 'v*.*.*' ]
|
tags: [ 'v*.*.*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -15,6 +19,10 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'backend/**'
|
- 'backend/**'
|
||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'Dockerfile.rootless'
|
||||||
|
- '.dockerignore'
|
||||||
|
- '.github/workflows'
|
||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -84,8 +92,8 @@ jobs:
|
|||||||
tags: ${{ steps.metadata.outputs.tags }}
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
labels: ${{ steps.metadata.outputs.labels }}
|
labels: ${{ steps.metadata.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
cache-from: type=gha
|
# cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
# cache-to: type=gha,mode=max
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ github.ref_name }}
|
VERSION=${{ github.ref_name }}
|
||||||
COMMIT=${{ github.sha }}
|
COMMIT=${{ github.sha }}
|
||||||
|
|||||||
12
.github/workflows/docker-publish.yaml
vendored
12
.github/workflows/docker-publish.yaml
vendored
@@ -8,6 +8,10 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'backend/**'
|
- 'backend/**'
|
||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'Dockerfile.rootless'
|
||||||
|
- '.dockerignore'
|
||||||
|
- '.github/workflows'
|
||||||
# Publish semver tags as releases.
|
# Publish semver tags as releases.
|
||||||
tags: [ 'v*.*.*' ]
|
tags: [ 'v*.*.*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -15,6 +19,10 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'backend/**'
|
- 'backend/**'
|
||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'Dockerfile.rootless'
|
||||||
|
- '.dockerignore'
|
||||||
|
- '.github/workflows'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Use docker.io for Docker Hub if empty
|
# Use docker.io for Docker Hub if empty
|
||||||
@@ -81,8 +89,8 @@ jobs:
|
|||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
cache-from: type=gha
|
# cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
# cache-to: type=gha,mode=max
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ github.ref_name }}
|
VERSION=${{ github.ref_name }}
|
||||||
COMMIT=${{ github.sha }}
|
COMMIT=${{ github.sha }}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -48,7 +48,7 @@ dist
|
|||||||
|
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
backend/app/api/app
|
backend/app/api/app
|
||||||
backend/app/api/__debug_bin
|
backend/app/api/__debug_bin*
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
# Nuxt Publish Dir
|
# Nuxt Publish Dir
|
||||||
|
|||||||
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -25,6 +25,7 @@
|
|||||||
"HBOX_STORAGE_DATA": "${workspaceRoot}/backend/.data",
|
"HBOX_STORAGE_DATA": "${workspaceRoot}/backend/.data",
|
||||||
"HBOX_STORAGE_SQLITE_URL": "${workspaceRoot}/backend/.data/homebox.db?_fk=1"
|
"HBOX_STORAGE_SQLITE_URL": "${workspaceRoot}/backend/.data/homebox.db?_fk=1"
|
||||||
},
|
},
|
||||||
|
"console": "integratedTerminal",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Launch Frontend",
|
"name": "Launch Frontend",
|
||||||
@@ -38,10 +39,11 @@
|
|||||||
"cwd": "${workspaceFolder}/frontend",
|
"cwd": "${workspaceFolder}/frontend",
|
||||||
"serverReadyAction": {
|
"serverReadyAction": {
|
||||||
"action": "debugWithChrome",
|
"action": "debugWithChrome",
|
||||||
"pattern": "Local: http://localhost:([0-9]+)",
|
"pattern": "Local: +http://localhost:([0-9]+)",
|
||||||
"uriFormat": "http://localhost:%s",
|
"uriFormat": "http://localhost:%s",
|
||||||
"webRoot": "${workspaceFolder}/frontend"
|
"webRoot": "${workspaceFolder}/frontend"
|
||||||
}
|
},
|
||||||
|
"console": "integratedTerminal",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
10
Dockerfile
10
Dockerfile
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
# Build Nuxt
|
# Build Nuxt
|
||||||
FROM node:18-alpine as frontend-builder
|
FROM node:18-alpine AS frontend-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||||
@@ -27,6 +27,8 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
|
|||||||
-o /go/bin/api \
|
-o /go/bin/api \
|
||||||
-v ./app/api/*.go
|
-v ./app/api/*.go
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/java:latest
|
||||||
|
|
||||||
# Production Stage
|
# Production Stage
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
@@ -39,11 +41,17 @@ RUN mkdir /app
|
|||||||
COPY --from=builder /go/bin/api /app
|
COPY --from=builder /go/bin/api /app
|
||||||
|
|
||||||
RUN chmod +x /app/api
|
RUN chmod +x /app/api
|
||||||
|
RUN apk add --no-cache wget
|
||||||
|
|
||||||
LABEL Name=homebox Version=0.0.1
|
LABEL Name=homebox Version=0.0.1
|
||||||
LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox"
|
LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox"
|
||||||
EXPOSE 7745
|
EXPOSE 7745
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
HEALTHCHECK --interval=30s \
|
||||||
|
--timeout=5s \
|
||||||
|
--start-period=5s \
|
||||||
|
--retries=3 \
|
||||||
|
CMD [ "/usr/bin/wget", "--no-verbose", "--tries=1", "-O -", "http://localhost:7745/api/v1/status" ]
|
||||||
VOLUME [ "/data" ]
|
VOLUME [ "/data" ]
|
||||||
|
|
||||||
ENTRYPOINT [ "/app/api" ]
|
ENTRYPOINT [ "/app/api" ]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
# Build Nuxt
|
# Build Nuxt
|
||||||
FROM node:18-alpine as frontend-builder
|
FROM node:18-alpine AS frontend-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||||
@@ -13,6 +13,7 @@ FROM golang:alpine AS builder
|
|||||||
ARG BUILD_TIME
|
ARG BUILD_TIME
|
||||||
ARG COMMIT
|
ARG COMMIT
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
|
ARG BUSYBOX_VERSION=1.36.1-r31
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk upgrade && \
|
apk upgrade && \
|
||||||
apk add --update git build-base gcc g++
|
apk add --update git build-base gcc g++
|
||||||
@@ -30,8 +31,10 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
|
|||||||
# create a directory so that we can copy it in the next stage
|
# create a directory so that we can copy it in the next stage
|
||||||
mkdir /data
|
mkdir /data
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/java:latest
|
||||||
|
|
||||||
# Production Stage
|
# Production Stage
|
||||||
FROM gcr.io/distroless/static
|
FROM gcr.io/distroless/static:latest
|
||||||
|
|
||||||
ENV HBOX_MODE=production
|
ENV HBOX_MODE=production
|
||||||
ENV HBOX_STORAGE_DATA=/data/
|
ENV HBOX_STORAGE_DATA=/data/
|
||||||
@@ -42,9 +45,16 @@ ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
|
|||||||
COPY --from=builder --chown=nonroot /go/bin/api /app
|
COPY --from=builder --chown=nonroot /go/bin/api /app
|
||||||
COPY --from=builder --chown=nonroot /data /data
|
COPY --from=builder --chown=nonroot /data /data
|
||||||
|
|
||||||
|
RUN apk add --no-cache wget
|
||||||
|
|
||||||
LABEL Name=homebox Version=0.0.1
|
LABEL Name=homebox Version=0.0.1
|
||||||
LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox"
|
LABEL org.opencontainers.image.source="https://github.com/sysadminsmedia/homebox"
|
||||||
EXPOSE 7745
|
EXPOSE 7745
|
||||||
|
HEALTHCHECK --interval=30s \
|
||||||
|
--timeout=5s \
|
||||||
|
--start-period=5s \
|
||||||
|
--retries=3 \
|
||||||
|
CMD [ "/usr/bin/wget", "--no-verbose", "--tries=1", "-O -", "http://localhost:7745/api/v1/status" ]
|
||||||
VOLUME [ "/data" ]
|
VOLUME [ "/data" ]
|
||||||
|
|
||||||
# Drop root and run as low-privileged user
|
# Drop root and run as low-privileged user
|
||||||
|
|||||||
@@ -16,13 +16,15 @@
|
|||||||
Homebox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind:
|
Homebox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind:
|
||||||
|
|
||||||
- _Simple_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. Use either a single docker container, or deploy yourself by compiling the binary for your platform of choice.
|
- _Simple_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. Use either a single docker container, or deploy yourself by compiling the binary for your platform of choice.
|
||||||
- _Blazingly Fast_ - Homebox is written in Go, which makes it extremely fast and requires minimal resources to deploy. In general idle memory usage is less than 50MB for the whole container.
|
- _Blazingly Fast_ - Homebox is written in Go, which makes it extremely fast and requires minimal resources to deploy. In general, idle memory usage is less than 50MB for the whole container.
|
||||||
- _Portable_ - Homebox is designed to be portable and run on anywhere. We use SQLite and an embedded Web UI to make it easy to deploy, use, and backup.
|
- _Portable_ - Homebox is designed to be portable and run on anywhere. We use SQLite and an embedded Web UI to make it easy to deploy, use, and backup.
|
||||||
|
|
||||||
|
# Screenshots
|
||||||
|
Check out screenshots of the project [here](https://imgur.com/a/5gLWt2j).
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
[Configuration & Docker Compose](https://homebox.sysadminsmedia.com/quick-start.html)
|
[Configuration & Docker Compose](https://homebox.sysadminsmedia.com/en/quick-start.html)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# If using the rootless image, ensure data
|
# If using the rootless image, ensure data
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ Since this software is still considered beta/WIP support is always only given fo
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please open a normal public issue if you have any security related concerns.
|
Please open a normal public issue for minor security issues or general security inquires.
|
||||||
|
|
||||||
|
For major or critical security issues, please open a private github security issue.
|
||||||
@@ -87,12 +87,6 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func BaseURLFunc(prefix string) func(s string) string {
|
|
||||||
return func(s string) string {
|
|
||||||
return prefix + "/v1" + s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, options ...func(*V1Controller)) *V1Controller {
|
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, options ...func(*V1Controller)) *V1Controller {
|
||||||
ctrl := &V1Controller{
|
ctrl := &V1Controller{
|
||||||
repo: repos,
|
repo: repos,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
|
|||||||
Search: params.Get("q"),
|
Search: params.Get("q"),
|
||||||
LocationIDs: queryUUIDList(params, "locations"),
|
LocationIDs: queryUUIDList(params, "locations"),
|
||||||
LabelIDs: queryUUIDList(params, "labels"),
|
LabelIDs: queryUUIDList(params, "labels"),
|
||||||
|
NegateLabels: queryBool(params.Get("negateLabels")),
|
||||||
ParentItemIDs: queryUUIDList(params, "parentIds"),
|
ParentItemIDs: queryUUIDList(params, "parentIds"),
|
||||||
IncludeArchived: queryBool(params.Get("includeArchived")),
|
IncludeArchived: queryBool(params.Get("includeArchived")),
|
||||||
Fields: filterFieldItems(params["fields"]),
|
Fields: filterFieldItems(params["fields"]),
|
||||||
@@ -80,6 +82,14 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
|
|||||||
ctx := services.NewContext(r.Context())
|
ctx := services.NewContext(r.Context())
|
||||||
|
|
||||||
items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r))
|
items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r))
|
||||||
|
totalPrice := new(big.Int)
|
||||||
|
for _, item := range items.Items {
|
||||||
|
totalPrice.Add(totalPrice, big.NewInt(int64(item.PurchasePrice*100)))
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPriceFloat := new(big.Float).SetInt(totalPrice)
|
||||||
|
totalPriceFloat.Quo(totalPriceFloat, big.NewFloat(100))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{
|
return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -83,6 +85,32 @@ func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
|
|||||||
return adapters.CommandID("id", fn, http.StatusNoContent)
|
return adapters.CommandID("id", fn, http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctrl *V1Controller) GetLocationWithPrice(auth context.Context, GID uuid.UUID, ID uuid.UUID) (repo.LocationOut, error) {
|
||||||
|
var location, err = ctrl.repo.Locations.GetOneByGroup(auth, GID, ID)
|
||||||
|
|
||||||
|
// Add direct child items price
|
||||||
|
totalPrice := new(big.Int)
|
||||||
|
items, err := ctrl.repo.Items.QueryByGroup(auth, GID, repo.ItemQuery{LocationIDs: []uuid.UUID{ID}})
|
||||||
|
for _, item := range items.Items {
|
||||||
|
totalPrice.Add(totalPrice, big.NewInt(int64(item.PurchasePrice*100)))
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPriceFloat := new(big.Float).SetInt(totalPrice)
|
||||||
|
totalPriceFloat.Quo(totalPriceFloat, big.NewFloat(100))
|
||||||
|
location.TotalPrice, _ = totalPriceFloat.Float64()
|
||||||
|
|
||||||
|
// Add price from child locations
|
||||||
|
for _, childLocation := range location.Children {
|
||||||
|
var childLocation, err = ctrl.GetLocationWithPrice(auth, GID, childLocation.ID)
|
||||||
|
if err != nil {
|
||||||
|
return repo.LocationOut{}, err
|
||||||
|
}
|
||||||
|
location.TotalPrice += childLocation.TotalPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
return location, err
|
||||||
|
}
|
||||||
|
|
||||||
// HandleLocationGet godoc
|
// HandleLocationGet godoc
|
||||||
//
|
//
|
||||||
// @Summary Get Location
|
// @Summary Get Location
|
||||||
@@ -95,7 +123,9 @@ func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
|
|||||||
func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc {
|
||||||
fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) {
|
fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) {
|
||||||
auth := services.NewContext(r.Context())
|
auth := services.NewContext(r.Context())
|
||||||
return ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID)
|
var location, err = ctrl.GetLocationWithPrice(auth, auth.GID, ID)
|
||||||
|
|
||||||
|
return location, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return adapters.CommandID("id", fn, http.StatusOK)
|
return adapters.CommandID("id", fn, http.StatusOK)
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
// API Version 1
|
// API Version 1
|
||||||
|
|
||||||
v1Base := v1.BaseURLFunc(prefix)
|
|
||||||
|
|
||||||
v1Ctrl := v1.NewControllerV1(
|
v1Ctrl := v1.NewControllerV1(
|
||||||
a.services,
|
a.services,
|
||||||
a.repos,
|
a.repos,
|
||||||
@@ -58,110 +56,111 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
|||||||
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
|
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
|
||||||
)
|
)
|
||||||
|
|
||||||
r.Get(v1Base("/status"), chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
r.Route(prefix+"/v1", func(r chi.Router) {
|
||||||
Version: version,
|
r.Get("/status", chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
||||||
Commit: commit,
|
Version: version,
|
||||||
BuildTime: buildTime,
|
Commit: commit,
|
||||||
})))
|
BuildTime: buildTime,
|
||||||
|
})))
|
||||||
|
|
||||||
r.Get(v1Base("/currencies"), chain.ToHandlerFunc(v1Ctrl.HandleCurrency()))
|
r.Get("/currencies", chain.ToHandlerFunc(v1Ctrl.HandleCurrency()))
|
||||||
|
|
||||||
providers := []v1.AuthProvider{
|
providers := []v1.AuthProvider{
|
||||||
providers.NewLocalProvider(a.services.User),
|
providers.NewLocalProvider(a.services.User),
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
|
r.Post("/users/register", chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
|
||||||
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
|
r.Post("/users/login", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
|
||||||
|
|
||||||
userMW := []errchain.Middleware{
|
userMW := []errchain.Middleware{
|
||||||
a.mwAuthToken,
|
a.mwAuthToken,
|
||||||
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
|
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Get(v1Base("/ws/events"), chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...))
|
r.Get("/ws/events", chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...))
|
||||||
r.Get(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
|
r.Get("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
|
||||||
r.Put(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
|
r.Put("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
|
||||||
r.Delete(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
|
r.Delete("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
|
||||||
r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
|
r.Post("/users/logout", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
|
||||||
r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
|
r.Get("/users/refresh", chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
|
||||||
r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
|
r.Put("/users/self/change-password", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
|
||||||
|
|
||||||
r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
|
r.Post("/groups/invitations", chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
|
||||||
r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
|
r.Get("/groups/statistics", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
|
||||||
r.Get(v1Base("/groups/statistics/purchase-price"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
|
r.Get("/groups/statistics/purchase-price", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
|
||||||
r.Get(v1Base("/groups/statistics/locations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
|
r.Get("/groups/statistics/locations", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
|
||||||
r.Get(v1Base("/groups/statistics/labels"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
|
r.Get("/groups/statistics/labels", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
|
||||||
|
|
||||||
// TODO: I don't like /groups being the URL for users
|
// TODO: I don't like /groups being the URL for users
|
||||||
r.Get(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
|
r.Get("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
|
||||||
r.Put(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
|
r.Put("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
|
||||||
|
|
||||||
r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
|
r.Post("/actions/ensure-asset-ids", chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
|
||||||
r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
|
r.Post("/actions/zero-item-time-fields", chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
|
||||||
r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
|
r.Post("/actions/ensure-import-refs", chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
|
||||||
r.Post(v1Base("/actions/set-primary-photos"), chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
|
r.Post("/actions/set-primary-photos", chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
|
||||||
|
|
||||||
r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
|
r.Get("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
|
||||||
r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
|
r.Post("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
|
||||||
r.Get(v1Base("/locations/tree"), chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
|
r.Get("/locations/tree", chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
|
||||||
r.Get(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...))
|
r.Get("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...))
|
||||||
r.Put(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
|
r.Put("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
|
||||||
r.Delete(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
|
r.Delete("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
|
||||||
|
|
||||||
r.Get(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
|
r.Get("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
|
||||||
r.Post(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
|
r.Post("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
|
||||||
r.Get(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
|
r.Get("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
|
||||||
r.Put(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
|
r.Put("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
|
||||||
r.Delete(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
|
r.Delete("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
|
||||||
|
|
||||||
r.Get(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
|
r.Get("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
|
||||||
r.Post(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
|
r.Post("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
|
||||||
r.Post(v1Base("/items/import"), chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
|
r.Post("/items/import", chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
|
||||||
r.Get(v1Base("/items/export"), chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...))
|
r.Get("/items/export", chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...))
|
||||||
r.Get(v1Base("/items/fields"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...))
|
r.Get("/items/fields", chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...))
|
||||||
r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
|
r.Get("/items/fields/values", chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
|
||||||
|
|
||||||
r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...))
|
r.Get("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...))
|
||||||
r.Get(v1Base("/items/{id}/path"), chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...))
|
r.Get("/items/{id}/path", chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...))
|
||||||
r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...))
|
r.Put("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...))
|
||||||
r.Patch(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...))
|
r.Patch("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...))
|
||||||
r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
|
r.Delete("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
|
||||||
|
|
||||||
r.Post(v1Base("/items/{id}/attachments"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
|
r.Post("/items/{id}/attachments", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
|
||||||
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
|
r.Put("/items/{id}/attachments/{attachment_id}", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
|
||||||
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
|
r.Delete("/items/{id}/attachments/{attachment_id}", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
|
||||||
|
|
||||||
r.Get(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
|
r.Get("/items/{id}/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
|
||||||
r.Post(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
|
r.Post("/items/{id}/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
|
||||||
r.Put(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
|
r.Put("/items/{id}/maintenance/{entry_id}", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
|
||||||
r.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
|
r.Delete("/items/{id}/maintenance/{entry_id}", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
|
||||||
|
|
||||||
r.Get(v1Base("/assets/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
|
r.Get("/assets/{id}", chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
|
||||||
|
|
||||||
// Notifiers
|
// Notifiers
|
||||||
r.Get(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
|
r.Get("/notifiers", chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
|
||||||
r.Post(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))
|
r.Post("/notifiers", chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))
|
||||||
r.Put(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...))
|
r.Put("/notifiers/{id}", chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...))
|
||||||
r.Delete(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...))
|
r.Delete("/notifiers/{id}", chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...))
|
||||||
r.Post(v1Base("/notifiers/test"), chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...))
|
r.Post("/notifiers/test", chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...))
|
||||||
|
|
||||||
// Asset-Like endpoints
|
// Asset-Like endpoints
|
||||||
assetMW := []errchain.Middleware{
|
assetMW := []errchain.Middleware{
|
||||||
a.mwAuthToken,
|
a.mwAuthToken,
|
||||||
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
|
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Get(
|
r.Get("/qrcode", chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...))
|
||||||
v1Base("/qrcode"),
|
r.Get(
|
||||||
chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...),
|
"/items/{id}/attachments/{attachment_id}",
|
||||||
)
|
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
|
||||||
r.Get(
|
)
|
||||||
v1Base("/items/{id}/attachments/{attachment_id}"),
|
|
||||||
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reporting Services
|
// Reporting Services
|
||||||
r.Get(v1Base("/reporting/bill-of-materials"), chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
|
r.Get("/reporting/bill-of-materials", chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
|
||||||
|
|
||||||
|
r.NotFound(http.NotFound)
|
||||||
|
})
|
||||||
|
|
||||||
r.NotFound(chain.ToHandlerFunc(notFoundHandler()))
|
r.NotFound(chain.ToHandlerFunc(notFoundHandler()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2469,6 +2469,9 @@
|
|||||||
"parent": {
|
"parent": {
|
||||||
"$ref": "#/definitions/repo.LocationSummary"
|
"$ref": "#/definitions/repo.LocationSummary"
|
||||||
},
|
},
|
||||||
|
"totalPrice": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -2707,6 +2710,9 @@
|
|||||||
},
|
},
|
||||||
"total": {
|
"total": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"totalPrice": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2989,4 +2995,4 @@
|
|||||||
"in": "header"
|
"in": "header"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.18.0
|
github.com/go-playground/validator/v10 v10.18.0
|
||||||
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
|
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/schema v1.2.1
|
github.com/gorilla/schema v1.4.1
|
||||||
github.com/hay-kot/httpkit v0.0.9
|
github.com/hay-kot/httpkit v0.0.9
|
||||||
github.com/mattn/go-sqlite3 v1.14.22
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/olahol/melody v1.1.4
|
github.com/olahol/melody v1.1.4
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu
|
|||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM=
|
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||||
github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
|||||||
@@ -17,6 +17,18 @@
|
|||||||
"symbol": "؋",
|
"symbol": "؋",
|
||||||
"name": "Afghan Afghani"
|
"name": "Afghan Afghani"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"code": "XCD",
|
||||||
|
"local": "Eastern Carribean",
|
||||||
|
"symbol": "$",
|
||||||
|
"name": "Eastern Carribean Dollar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "XOF",
|
||||||
|
"local": "CFA Franc",
|
||||||
|
"symbol": "CFA",
|
||||||
|
"name": "CFA Franc"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"code": "ALL",
|
"code": "ALL",
|
||||||
"local": "Albania",
|
"local": "Albania",
|
||||||
@@ -167,12 +179,24 @@
|
|||||||
"symbol": "FC",
|
"symbol": "FC",
|
||||||
"name": "Congolese Franc"
|
"name": "Congolese Franc"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"code": "XAF",
|
||||||
|
"local": "CFA",
|
||||||
|
"symbol": "FCFA",
|
||||||
|
"name": "CFA Franc BEAC"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"code": "CHF",
|
"code": "CHF",
|
||||||
"local": "Switzerland",
|
"local": "Switzerland",
|
||||||
"symbol": "CHF",
|
"symbol": "CHF",
|
||||||
"name": "Swiss Franc"
|
"name": "Swiss Franc"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"code": "NZD",
|
||||||
|
"local": "New Zealand",
|
||||||
|
"symbol": "NZ$",
|
||||||
|
"name": "New Zealand Dollar"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"code": "CLP",
|
"code": "CLP",
|
||||||
"local": "Chile",
|
"local": "Chile",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type (
|
|||||||
AssetID AssetID `json:"assetId"`
|
AssetID AssetID `json:"assetId"`
|
||||||
LocationIDs []uuid.UUID `json:"locationIds"`
|
LocationIDs []uuid.UUID `json:"locationIds"`
|
||||||
LabelIDs []uuid.UUID `json:"labelIds"`
|
LabelIDs []uuid.UUID `json:"labelIds"`
|
||||||
|
NegateLabels bool `json:"negateLabels"`
|
||||||
ParentItemIDs []uuid.UUID `json:"parentIds"`
|
ParentItemIDs []uuid.UUID `json:"parentIds"`
|
||||||
SortBy string `json:"sortBy"`
|
SortBy string `json:"sortBy"`
|
||||||
IncludeArchived bool `json:"includeArchived"`
|
IncludeArchived bool `json:"includeArchived"`
|
||||||
@@ -365,10 +366,17 @@ func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q Ite
|
|||||||
if len(q.LabelIDs) > 0 {
|
if len(q.LabelIDs) > 0 {
|
||||||
labelPredicates := make([]predicate.Item, 0, len(q.LabelIDs))
|
labelPredicates := make([]predicate.Item, 0, len(q.LabelIDs))
|
||||||
for _, l := range q.LabelIDs {
|
for _, l := range q.LabelIDs {
|
||||||
labelPredicates = append(labelPredicates, item.HasLabelWith(label.ID(l)))
|
if !q.NegateLabels {
|
||||||
|
labelPredicates = append(labelPredicates, item.HasLabelWith(label.ID(l)))
|
||||||
|
} else {
|
||||||
|
labelPredicates = append(labelPredicates, item.Not(item.HasLabelWith(label.ID(l))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !q.NegateLabels {
|
||||||
|
andPredicates = append(andPredicates, item.Or(labelPredicates...))
|
||||||
|
} else {
|
||||||
|
andPredicates = append(andPredicates, item.And(labelPredicates...))
|
||||||
}
|
}
|
||||||
|
|
||||||
andPredicates = append(andPredicates, item.Or(labelPredicates...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(q.LocationIDs) > 0 {
|
if len(q.LocationIDs) > 0 {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type (
|
|||||||
Parent *LocationSummary `json:"parent,omitempty"`
|
Parent *LocationSummary `json:"parent,omitempty"`
|
||||||
LocationSummary
|
LocationSummary
|
||||||
Children []LocationSummary `json:"children"`
|
Children []LocationSummary `json:"children"`
|
||||||
|
TotalPrice float64 `json:"totalPrice"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ export default defineConfig({
|
|||||||
sitemap: {
|
sitemap: {
|
||||||
hostname: 'https://homebox.sysadminsmedia.com',
|
hostname: 'https://homebox.sysadminsmedia.com',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
locales: {
|
||||||
|
en: {
|
||||||
|
label: 'English',
|
||||||
|
lang: 'en',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
logo: '/lilbox.svg',
|
logo: '/lilbox.svg',
|
||||||
|
|
||||||
@@ -19,26 +27,33 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
// https://vitepress.dev/reference/default-theme-config
|
// https://vitepress.dev/reference/default-theme-config
|
||||||
nav: [
|
nav: [
|
||||||
{ text: 'Home', link: '/' },
|
|
||||||
{ text: 'API', link: 'https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/sysadminsmedia/homebox/main/docs/docs/api/openapi-2.0.json' }
|
{ text: 'API', link: 'https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/sysadminsmedia/homebox/main/docs/docs/api/openapi-2.0.json' }
|
||||||
],
|
],
|
||||||
|
|
||||||
sidebar: [
|
sidebar: {
|
||||||
{
|
'/en/': [
|
||||||
text: 'Getting Started',
|
{
|
||||||
items: [
|
text: 'Getting Started',
|
||||||
{ text: 'Quick Start', link: '/quick-start' },
|
items: [
|
||||||
{ text: 'Tips and Tricks', link: '/tips-tricks' }
|
{ text: 'Quick Start', link: '/en/quick-start' },
|
||||||
]
|
{ text: 'Tips and Tricks', link: '/en/tips-tricks' }
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
text: 'Advanced',
|
{
|
||||||
items: [
|
text: 'Advanced',
|
||||||
{ text: 'Import CSV', link: '/import-csv' },
|
items: [
|
||||||
{ text: 'Build from Source', link: '/build' }
|
{ text: 'Import CSV', link: '/en/import-csv' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
|
text: 'Contributing',
|
||||||
|
items: [
|
||||||
|
{ text: 'Get Started', link: '/en/contribute/get-started' },
|
||||||
|
{ text: 'Bounty Program', link: '/en/contribute/bounty' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
socialLinks: [
|
socialLinks: [
|
||||||
{ icon: 'discord', link: 'https://discord.gg/aY4DCkpNA9' },
|
{ icon: 'discord', link: 'https://discord.gg/aY4DCkpNA9' },
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# Building The Binary
|
|
||||||
|
|
||||||
This document describes how to build the project from source code.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@@ -1683,14 +1683,12 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "admin@admin.com",
|
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"name": "username",
|
"name": "username",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "admin",
|
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"name": "password",
|
"name": "password",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
@@ -2469,6 +2467,9 @@
|
|||||||
"parent": {
|
"parent": {
|
||||||
"$ref": "#/definitions/repo.LocationSummary"
|
"$ref": "#/definitions/repo.LocationSummary"
|
||||||
},
|
},
|
||||||
|
"totalPrice": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -2707,6 +2708,9 @@
|
|||||||
},
|
},
|
||||||
"total": {
|
"total": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"totalPrice": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2989,4 +2993,4 @@
|
|||||||
"in": "header"
|
"in": "header"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
docs/en/contribute/bounty.md
Normal file
19
docs/en/contribute/bounty.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Bounty Program
|
||||||
|
|
||||||
|
## About
|
||||||
|
As part of our commitment to open source, and building an active community around Homebox (and hopefully active pool of developers), we are enabling bounties on issues.
|
||||||
|
|
||||||
|
After digging through several platforms, we ended up settling on [boss.dev](https://www.boss.dev/) as it has some of the lowest fees we could possibly find for any of these platforms other than spinning one up ourselves (which we currently aren't in a position to do).
|
||||||
|
|
||||||
|
While it's not the perfect solution, we think it's about the best one we could find at the moment to lower the rates as much as possible to make sure everyone get's the highest payouts possible. (Some we found were as high as a combined 16%!!!)
|
||||||
|
|
||||||
|
We hope that by enabling bounties on issues, people who have the means and want certain features implemented quicker can now sponsor issues, and in turn everyone contributing code can potentially earn some money for their hard work.
|
||||||
|
|
||||||
|
## Contributor
|
||||||
|
As a contributor wanting to accept money from bounties all you need to do is simply register for an account via GitHub, and attach a bank account (or debit card in the USA).
|
||||||
|
|
||||||
|
## Sponsor
|
||||||
|
Sign in with a GitHub account, and then attach a credit card to your account.
|
||||||
|
|
||||||
|
## Commands to use boss.dev
|
||||||
|
There is documentation on their website regarding commands that you can put in comments to use the bounty system. [boss.dev Documentation](https://www.boss.dev/doc)
|
||||||
69
docs/en/contribute/get-started.md
Normal file
69
docs/en/contribute/get-started.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Getting Started With Contributing
|
||||||
|
|
||||||
|
## Get Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
There is a devcontainer available for this project. If you are using VSCode, you can use the devcontainer to get started. If you are not using VSCode, you need to ensure that you have the following tools installed:
|
||||||
|
|
||||||
|
- [Go 1.19+](https://golang.org/doc/install)
|
||||||
|
- [Swaggo](https://github.com/swaggo/swag)
|
||||||
|
- [Node.js 16+](https://nodejs.org/en/download/)
|
||||||
|
- [pnpm](https://pnpm.io/installation)
|
||||||
|
- [Taskfile](https://taskfile.dev/#/installation) (Optional but recommended)
|
||||||
|
- For code generation, you'll need to have `python3` available on your path. In most cases, this is already installed and available.
|
||||||
|
|
||||||
|
If you're using `taskfile` you can run `task --list-all` for a list of all commands and their descriptions.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
If you're using the taskfile, you can use the `task setup` command to run the required setup commands. Otherwise, you can review the commands required in the `Taskfile.yml` file.
|
||||||
|
|
||||||
|
Note that when installing dependencies with pnpm, you must use the `--shamefully-hoist` flag. If you don't use this flag, you will get an error when running the frontend server.
|
||||||
|
|
||||||
|
### API Development Notes
|
||||||
|
start command `task go:run`
|
||||||
|
|
||||||
|
1. API Server does not auto reload. You'll need to restart the server after making changes.
|
||||||
|
2. Unit tests should be written in Go, however, end-to-end or user story tests should be written in TypeScript using the client library in the frontend directory.
|
||||||
|
|
||||||
|
test command `task go:test`
|
||||||
|
|
||||||
|
lint command `task go:lint`
|
||||||
|
|
||||||
|
swagger update command `task swag`
|
||||||
|
|
||||||
|
### Frontend Development Notes
|
||||||
|
|
||||||
|
start command `task: ui:dev`
|
||||||
|
|
||||||
|
1. The frontend is a Vue 3 app with Nuxt.js that uses Tailwind and DaisyUI for styling.
|
||||||
|
2. We're using Vitest for our automated testing. You can run these with `task ui:watch`.
|
||||||
|
3. Tests require the API server to be running, and in some cases the first run will fail due to a race condition. If this happens, just run the tests again and they should pass.
|
||||||
|
|
||||||
|
fix/lint code `task ui:fix`
|
||||||
|
|
||||||
|
type checking `task ui:check`
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
We use [Vitepress](https://vitepress.dev/) for the web documentation of homebox. Anyone is welcome to contribute the documentation if they wish.
|
||||||
|
For documentation contributions you only need NodeJS and PNPM.
|
||||||
|
|
||||||
|
::: info Notes
|
||||||
|
- Languages are seperated by folder (e.g `/en`, `/fr`, etc.)
|
||||||
|
- The Sidebar must be updated on a per language basis
|
||||||
|
- Each languages files can be named independently (slugs can match the language)
|
||||||
|
- The `public/_redirects` file is used to redirect the default to english
|
||||||
|
- Redirects can also be configured per language by adding `Language=` after the redirect code
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Branch Flow
|
||||||
|
We use the `main` branch as the development branch. All PRs should be made to the `main` branch form a feature branch.
|
||||||
|
To create a pull request you can use the following steps:
|
||||||
|
|
||||||
|
1. Fork the repo and create a new branch from `main`
|
||||||
|
2. If you added code that should be tested, add tests
|
||||||
|
3. If you've changed APIs update the documentation
|
||||||
|
4. Ensure that the test suite and linters pass
|
||||||
|
5. Create your PR
|
||||||
|
|
||||||
@@ -11,10 +11,10 @@ hero:
|
|||||||
actions:
|
actions:
|
||||||
- theme: brand
|
- theme: brand
|
||||||
text: Quick Start
|
text: Quick Start
|
||||||
link: /quick-start
|
link: /en/quick-start
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: Tips and Tricks
|
text: Tips and Tricks
|
||||||
link: /tips-tricks
|
link: /en/tips-tricks
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: Add/Update/Delete Items
|
- title: Add/Update/Delete Items
|
||||||
@@ -46,11 +46,14 @@ volumes:
|
|||||||
homebox-data:
|
homebox-data:
|
||||||
driver: local
|
driver: local
|
||||||
```
|
```
|
||||||
|
|
||||||
::: info
|
::: info
|
||||||
If you use the `rootless` image, and instead of using named volumes you would prefer using a hostMount directly (e.g., `volumes: [ /path/to/data/folder:/data ]`) you need to `chown` the chosen directory in advance to the `65532` user (as shown in the Docker example above).
|
If you use the `rootless` image, and instead of using named volumes you would prefer using a hostMount directly (e.g., `volumes: [ /path/to/data/folder:/data ]`) you need to `chown` the chosen directory in advance to the `65532` user (as shown in the Docker example above).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
If you have previously set up docker compose with the `HBOX_WEB_READ_TIMEOUT`, `HBOX_WEB_WRITE_TIMEOUT`, or `HBOX_IDLE_TIMEOUT` options, and you were previously using the hay-kot image, please note that you will have to add an `s` for seconds or `m` for minutes to the end of the integers. A dependency update removed the defaultation to seconds and it now requires an explicit duration time.
|
||||||
|
:::
|
||||||
|
|
||||||
## Env Variables & Configuration
|
## Env Variables & Configuration
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
@@ -59,15 +62,15 @@ If you use the `rootless` image, and instead of using named volumes you would pr
|
|||||||
| HBOX_WEB_PORT | 7745 | port to run the web server on, if you're using docker do not change this |
|
| HBOX_WEB_PORT | 7745 | port to run the web server on, if you're using docker do not change this |
|
||||||
| HBOX_WEB_HOST | | host to run the web server on, if you're using docker do not change this |
|
| HBOX_WEB_HOST | | host to run the web server on, if you're using docker do not change this |
|
||||||
| HBOX_OPTIONS_ALLOW_REGISTRATION | true | allow users to register themselves |
|
| HBOX_OPTIONS_ALLOW_REGISTRATION | true | allow users to register themselves |
|
||||||
| HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID | true | auto increments the asset_id field for new items |
|
| HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID | true | auto-increments the asset_id field for new items |
|
||||||
| HBOX_OPTIONS_CURRENCY_CONFIG | | json configuration file containing additional currencie |
|
| HBOX_OPTIONS_CURRENCY_CONFIG | | json configuration file containing additional currencie |
|
||||||
| HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB |
|
| HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB |
|
||||||
| HBOX_WEB_READ_TIMEOUT | 10 | Read timeout of HTTP sever |
|
| HBOX_WEB_READ_TIMEOUT | 10s | Read timeout of HTTP sever |
|
||||||
| HBOX_WEB_WRITE_TIMEOUT | 10 | Write timeout of HTTP server |
|
| HBOX_WEB_WRITE_TIMEOUT | 10s | Write timeout of HTTP server |
|
||||||
| HBOX_WEB_IDLE_TIMEOUT | 30 | Idle timeout of HTTP server |
|
| HBOX_WEB_IDLE_TIMEOUT | 30s | Idle timeout of HTTP server |
|
||||||
| HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker |
|
| HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker |
|
||||||
| HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, if you're using docker do not change this |
|
| HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, if you're using docker do not change this |
|
||||||
| HBOX_LOG_LEVEL | info | log level to use, can be one of: trace, debug, info, warn, error, critical |
|
| HBOX_LOG_LEVEL | info | log level to use, can be one of trace, debug, info, warn, error, critical |
|
||||||
| HBOX_LOG_FORMAT | text | log format to use, can be one of: text, json |
|
| HBOX_LOG_FORMAT | text | log format to use, can be one of: text, json |
|
||||||
| HBOX_MAILER_HOST | | email host to use, if not set no email provider will be used |
|
| HBOX_MAILER_HOST | | email host to use, if not set no email provider will be used |
|
||||||
| HBOX_MAILER_PORT | 587 | email port to use |
|
| HBOX_MAILER_PORT | 587 | email port to use |
|
||||||
@@ -17,7 +17,7 @@ Homebox Custom Fields also have special support for URLs. Provide a URL (`https:
|
|||||||
|
|
||||||
## Managing Asset IDs
|
## Managing Asset IDs
|
||||||
|
|
||||||
Homebox provides the option to auto-set asset IDs, this is the default behavior. These can be used for tracking assets with printable tags or labels. You can disable this behavior via a command line flag or ENV variable. See [configuration](/quick-start#env-variables-configuration) for more details.
|
Homebox provides the option to auto-set asset IDs, this is the default behavior. These can be used for tracking assets with printable tags or labels. You can disable this behavior via a command line flag or ENV variable. See [configuration](/en/quick-start.md#env-variables-configuration) for more details.
|
||||||
|
|
||||||
Example ID: `000-001`
|
Example ID: `000-001`
|
||||||
|
|
||||||
4
docs/public/_redirects
Normal file
4
docs/public/_redirects
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/ /en/ 302
|
||||||
|
|
||||||
|
# This is an example for a french redirect
|
||||||
|
# /* /fr/:splat 302 Language=fr
|
||||||
44
fly.toml
44
fly.toml
@@ -1,44 +0,0 @@
|
|||||||
# fly.toml file generated for homebox on 2022-09-08T16:00:08-08:00
|
|
||||||
|
|
||||||
app = "homebox"
|
|
||||||
kill_signal = "SIGINT"
|
|
||||||
kill_timeout = 5
|
|
||||||
processes = []
|
|
||||||
|
|
||||||
[build.args]
|
|
||||||
COMMIT = "HEAD"
|
|
||||||
VERSION = "nightly"
|
|
||||||
|
|
||||||
[env]
|
|
||||||
PORT = "7745"
|
|
||||||
HBOX_DEMO = "true"
|
|
||||||
|
|
||||||
[experimental]
|
|
||||||
allowed_public_ports = []
|
|
||||||
auto_rollback = true
|
|
||||||
|
|
||||||
[[services]]
|
|
||||||
http_checks = []
|
|
||||||
internal_port = 7745
|
|
||||||
processes = ["app"]
|
|
||||||
protocol = "tcp"
|
|
||||||
script_checks = []
|
|
||||||
[services.concurrency]
|
|
||||||
hard_limit = 25
|
|
||||||
soft_limit = 20
|
|
||||||
type = "connections"
|
|
||||||
|
|
||||||
[[services.ports]]
|
|
||||||
force_https = true
|
|
||||||
handlers = ["http"]
|
|
||||||
port = 80
|
|
||||||
|
|
||||||
[[services.ports]]
|
|
||||||
handlers = ["tls", "http"]
|
|
||||||
port = 443
|
|
||||||
|
|
||||||
[[services.tcp_checks]]
|
|
||||||
grace_period = "1s"
|
|
||||||
interval = "15s"
|
|
||||||
restart_limit = 0
|
|
||||||
timeout = "2s"
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
/>
|
/>
|
||||||
<Currency v-else-if="detail.type == 'currency'" :amount="detail.text" />
|
<Currency v-else-if="detail.type == 'currency'" :amount="detail.text" />
|
||||||
<template v-else-if="detail.type === 'link'">
|
<template v-else-if="detail.type === 'link'">
|
||||||
<div class="tooltip tooltip-primary tooltip-right" :data-tip="detail.href">
|
<div class="tooltip tooltip-primary tooltip-top" :data-tip="detail.href">
|
||||||
<a class="btn btn-primary btn-xs" :href="detail.href" target="_blank">
|
<a class="btn btn-primary btn-xs" :href="detail.href" target="_blank">
|
||||||
<MdiOpenInNew class="mr-2 swap-on" />
|
<MdiOpenInNew class="mr-2 swap-on" />
|
||||||
{{ detail.text }}
|
{{ detail.text }}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type ItemsQuery = {
|
|||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
locations?: string[];
|
locations?: string[];
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
|
negateLabels?: boolean;
|
||||||
parentIds?: string[];
|
parentIds?: string[];
|
||||||
q?: string;
|
q?: string;
|
||||||
fields?: string[];
|
fields?: string[];
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ export interface LocationOut {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
parent: LocationSummary;
|
parent: LocationSummary;
|
||||||
|
totalPrice: number;
|
||||||
updatedAt: Date | string;
|
updatedAt: Date | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +330,7 @@ export interface PaginationResultItemSummary {
|
|||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
total: number;
|
total: number;
|
||||||
|
totalPrice: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TotalsByOrganizer {
|
export interface TotalsByOrganizer {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
export default defineNuxtRouteMiddleware(async () => {
|
export default defineNuxtRouteMiddleware(async () => {
|
||||||
const ctx = useAuthContext();
|
const ctx = useAuthContext();
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
const redirectTo = useState("authRedirect");
|
||||||
|
|
||||||
if (!ctx.isAuthorized()) {
|
if (!ctx.isAuthorized()) {
|
||||||
if (window.location.pathname !== "/") {
|
if (window.location.pathname !== "/") {
|
||||||
console.debug("[middleware/auth] isAuthorized returned false, redirecting to /");
|
console.debug("[middleware/auth] isAuthorized returned false, redirecting to /");
|
||||||
|
redirectTo.value = window.location.pathname;
|
||||||
return navigateTo("/");
|
return navigateTo("/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +17,7 @@ export default defineNuxtRouteMiddleware(async () => {
|
|||||||
if (error) {
|
if (error) {
|
||||||
if (window.location.pathname !== "/") {
|
if (window.location.pathname !== "/") {
|
||||||
console.debug("[middleware/user] user is null and fetch failed, redirecting to /");
|
console.debug("[middleware/user] user is null and fetch failed, redirecting to /");
|
||||||
|
redirectTo.value = window.location.pathname;
|
||||||
return navigateTo("/");
|
return navigateTo("/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const loginPassword = ref("");
|
const loginPassword = ref("");
|
||||||
|
const redirectTo = useState("authRedirect");
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -116,7 +117,8 @@
|
|||||||
|
|
||||||
toast.success("Logged in successfully");
|
toast.success("Logged in successfully");
|
||||||
|
|
||||||
navigateTo("/home");
|
navigateTo(redirectTo.value || "/home");
|
||||||
|
redirectTo.value = null;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,10 +158,10 @@
|
|||||||
<a href="https://twitter.com/haybytes" class="tooltip" data-tip="Follow The Developer" target="_blank">
|
<a href="https://twitter.com/haybytes" class="tooltip" data-tip="Follow The Developer" target="_blank">
|
||||||
<MdiTwitter class="h-8 w-8" />
|
<MdiTwitter class="h-8 w-8" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/tuncmNrE4z" class="tooltip" data-tip="Join The Discord" target="_blank">
|
<a href="https://discord.gg/aY4DCkpNA9" class="tooltip" data-tip="Join The Discord" target="_blank">
|
||||||
<MdiDiscord class="h-8 w-8" />
|
<MdiDiscord class="h-8 w-8" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hay-kot.github.io/homebox/" class="tooltip" data-tip="Read The Docs" target="_blank">
|
<a href="https://homebox.sysadminsmedia.com/en/" class="tooltip" data-tip="Read The Docs" target="_blank">
|
||||||
<MdiFolder class="h-8 w-8" />
|
<MdiFolder class="h-8 w-8" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
const advanced = useRouteQuery("advanced", false);
|
const advanced = useRouteQuery("advanced", false);
|
||||||
const includeArchived = useRouteQuery("archived", false);
|
const includeArchived = useRouteQuery("archived", false);
|
||||||
const fieldSelector = useRouteQuery("fieldSelector", false);
|
const fieldSelector = useRouteQuery("fieldSelector", false);
|
||||||
|
const negateLabels = useRouteQuery("negateLabels", false);
|
||||||
|
|
||||||
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
|
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
|
||||||
const hasNext = computed(() => page.value * pageSize.value < total.value);
|
const hasNext = computed(() => page.value * pageSize.value < total.value);
|
||||||
@@ -162,6 +163,12 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(negateLabels, (newV, oldV) => {
|
||||||
|
if (newV !== oldV) {
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function fetchValues(field: string): Promise<string[]> {
|
async function fetchValues(field: string): Promise<string[]> {
|
||||||
if (fieldValuesCache.value[field]) {
|
if (fieldValuesCache.value[field]) {
|
||||||
return fieldValuesCache.value[field];
|
return fieldValuesCache.value[field];
|
||||||
@@ -193,6 +200,7 @@
|
|||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
includeArchived: includeArchived.value ? "true" : "false",
|
includeArchived: includeArchived.value ? "true" : "false",
|
||||||
|
negateLabels: negateLabels.value ? "true" : "false",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -219,6 +227,7 @@
|
|||||||
q: query.value || "",
|
q: query.value || "",
|
||||||
locations: locIDs.value,
|
locations: locIDs.value,
|
||||||
labels: labIDs.value,
|
labels: labIDs.value,
|
||||||
|
negateLabels: negateLabels.value,
|
||||||
includeArchived: includeArchived.value,
|
includeArchived: includeArchived.value,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
@@ -268,6 +277,7 @@
|
|||||||
advanced: "true",
|
advanced: "true",
|
||||||
archived: includeArchived.value ? "true" : "false",
|
archived: includeArchived.value ? "true" : "false",
|
||||||
fieldSelector: fieldSelector.value ? "true" : "false",
|
fieldSelector: fieldSelector.value ? "true" : "false",
|
||||||
|
negateLabels: negateLabels.value ? "true" : "false",
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
q: query.value,
|
q: query.value,
|
||||||
@@ -359,6 +369,10 @@
|
|||||||
<input v-model="fieldSelector" type="checkbox" class="toggle toggle-sm toggle-primary" />
|
<input v-model="fieldSelector" type="checkbox" class="toggle toggle-sm toggle-primary" />
|
||||||
<span class="label-text ml-4"> Field Selector </span>
|
<span class="label-text ml-4"> Field Selector </span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="label cursor-pointer mr-auto">
|
||||||
|
<input v-model="negateLabels" type="checkbox" class="toggle toggle-sm toggle-primary" />
|
||||||
|
<span class="label-text ml-4"> Negate selected labels </span>
|
||||||
|
</label>
|
||||||
<hr class="my-2" />
|
<hr class="my-2" />
|
||||||
<BaseButton class="btn-block btn-sm" @click="reset"> Reset Search</BaseButton>
|
<BaseButton class="btn-block btn-sm" @click="reset"> Reset Search</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.data.items;
|
return resp.data;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -115,8 +115,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl pb-1">
|
<h1 class="text-2xl pb-1 flex items-center gap-3">
|
||||||
{{ label ? label.name : "" }}
|
{{ label ? label.name : "" }}
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="items && items.totalPrice"
|
||||||
|
class="text-xs bg-secondary text-secondary-content rounded-full px-2 py-1"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Currency :amount="items.totalPrice" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex gap-1 flex-wrap text-xs">
|
<div class="flex gap-1 flex-wrap text-xs">
|
||||||
<div>
|
<div>
|
||||||
@@ -144,7 +153,7 @@
|
|||||||
<Markdown v-if="label && label.description" class="text-base" :source="label.description"> </Markdown>
|
<Markdown v-if="label && label.description" class="text-base" :source="label.description"> </Markdown>
|
||||||
</div>
|
</div>
|
||||||
<section v-if="label && items">
|
<section v-if="label && items">
|
||||||
<ItemViewSelectable :items="items" />
|
<ItemViewSelectable :items="items.items" />
|
||||||
</section>
|
</section>
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
|
|||||||
@@ -138,8 +138,17 @@
|
|||||||
<li>{{ location.name }}</li>
|
<li>{{ location.name }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-2xl pb-1">
|
<h1 class="text-2xl pb-1 flex items-center gap-3">
|
||||||
{{ location ? location.name : "" }}
|
{{ location ? location.name : "" }}
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="location && location.totalPrice"
|
||||||
|
class="text-xs bg-secondary text-secondary-content rounded-full px-2 py-1"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Currency :amount="location.totalPrice" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex gap-1 flex-wrap text-xs">
|
<div class="flex gap-1 flex-wrap text-xs">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -14,5 +14,6 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vitepress": "^1.2.3"
|
"vitepress": "^1.2.3"
|
||||||
}
|
},
|
||||||
}
|
"packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": [
|
|
||||||
"config:base"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user