mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-24 14:31:55 +01:00
Compare commits
23 Commits
mk/kill-32
...
v0.22.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e501d769da | ||
|
|
7d0e05dc5d | ||
|
|
81233e2999 | ||
|
|
415c3238ae | ||
|
|
b3153cc971 | ||
|
|
0801df9961 | ||
|
|
2bdd085289 | ||
|
|
c30cac4489 | ||
|
|
397a1c6f3e | ||
|
|
05a392346f | ||
|
|
28c3e102a2 | ||
|
|
116e39531b | ||
|
|
8a90b9c133 | ||
|
|
ef52009f57 | ||
|
|
76154263e0 | ||
|
|
108194e7fd | ||
|
|
bf845ae0f7 | ||
|
|
9be6a8c888 | ||
|
|
38a987676e | ||
|
|
1f746efe27 | ||
|
|
d57bf8834b | ||
|
|
cb2c58c3f4 | ||
|
|
7b3cf0453e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -60,7 +60,7 @@ backend/app/api/static/public/*
|
||||
backend/api
|
||||
|
||||
docs/.vitepress/cache/
|
||||
/.data/
|
||||
.data/
|
||||
|
||||
# Playwright
|
||||
frontend/test-results/
|
||||
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -22,6 +22,7 @@
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"eslint.format.enable": true,
|
||||
"eslint.validate": ["javascript", "typescript", "vue"],
|
||||
"eslint.useFlatConfig": true,
|
||||
"css.validate": false,
|
||||
"tailwindCSS.includeLanguages": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Node dependencies stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally (caching layer)
|
||||
@@ -10,7 +10,7 @@ COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Build Nuxt (frontend) stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally again (it can reuse the cache if not changed)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ---------------------------------------
|
||||
# Node dependencies stage
|
||||
# ---------------------------------------
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally (caching layer)
|
||||
@@ -14,7 +14,7 @@ RUN pnpm install --frozen-lockfile
|
||||
# ---------------------------------------
|
||||
# Build Nuxt (frontend) stage
|
||||
# ---------------------------------------
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally again (it can reuse the cache if not changed)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Node dependencies stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally (caching layer)
|
||||
@@ -10,7 +10,7 @@ COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Build Nuxt (frontend) stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally again (it can reuse the cache if not changed)
|
||||
|
||||
106
backend/go.mod
106
backend/go.mod
@@ -6,15 +6,15 @@ toolchain go1.24.3
|
||||
|
||||
require (
|
||||
entgo.io/ent v0.14.5
|
||||
github.com/ardanlabs/conf/v3 v3.8.0
|
||||
github.com/ardanlabs/conf/v3 v3.9.0
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/evanoberholster/imagemeta v0.3.1
|
||||
github.com/gen2brain/avif v0.4.4
|
||||
github.com/gen2brain/heic v0.4.5
|
||||
github.com/gen2brain/jpegxl v0.4.5
|
||||
github.com/gen2brain/webp v0.5.5
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-playground/validator/v10 v10.27.0
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -22,13 +22,13 @@ require (
|
||||
github.com/hay-kot/httpkit v0.0.11
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/olahol/melody v1.3.0
|
||||
github.com/olahol/melody v1.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pressly/goose/v3 v3.24.3
|
||||
github.com/pressly/goose/v3 v3.26.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.7
|
||||
github.com/shirou/gopsutil/v4 v4.25.10
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2
|
||||
github.com/swaggo/swag v1.16.6
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.5
|
||||
@@ -39,19 +39,19 @@ require (
|
||||
gocloud.dev/pubsub/kafkapubsub v0.43.0
|
||||
gocloud.dev/pubsub/natspubsub v0.43.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.43.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/image v0.30.0
|
||||
golang.org/x/text v0.28.0
|
||||
modernc.org/sqlite v1.38.2
|
||||
golang.org/x/crypto v0.44.0
|
||||
golang.org/x/image v0.33.0
|
||||
golang.org/x/text v0.31.0
|
||||
modernc.org/sqlite v1.40.0
|
||||
)
|
||||
|
||||
require (
|
||||
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go v0.121.4 // indirect
|
||||
cloud.google.com/go/auth v0.16.4 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/pubsub v1.49.0 // indirect
|
||||
@@ -69,7 +69,7 @@ require (
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
|
||||
github.com/IBM/sarama v1.45.2 // indirect
|
||||
github.com/IBM/sarama v1.46.3 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
@@ -103,22 +103,29 @@ require (
|
||||
github.com/eapache/go-resiliency v1.7.0 // indirect
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.3 // indirect
|
||||
github.com/go-openapi/spec v0.22.1 // indirect
|
||||
github.com/go-openapi/swag v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
|
||||
@@ -126,7 +133,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/wire v0.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@@ -140,34 +147,34 @@ require (
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/nats-io/nats.go v1.44.0 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.10.1 // indirect
|
||||
github.com/tinylib/msgp v1.5.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||
@@ -175,33 +182,34 @@ require (
|
||||
github.com/zclconf/go-cty v1.14.4 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/oauth2 v0.33.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/api v0.247.0 // indirect
|
||||
google.golang.org/api v0.256.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.66.7 // indirect
|
||||
modernc.org/libc v1.67.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
117
backend/go.sum
117
backend/go.sum
@@ -6,10 +6,14 @@ cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs=
|
||||
cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s=
|
||||
cloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8=
|
||||
cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
|
||||
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
|
||||
@@ -71,6 +75,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
|
||||
github.com/IBM/sarama v1.45.2 h1:8m8LcMCu3REcwpa7fCP6v2fuPuzVwXDAM2DOv3CBrKw=
|
||||
github.com/IBM/sarama v1.45.2/go.mod h1:ppaoTcVdGv186/z6MEKsMm70A5fwJfRTpstI37kVn3Y=
|
||||
github.com/IBM/sarama v1.46.3 h1:njRsX6jNlnR+ClJ8XmkO+CM4unbrNr/2vB5KK6UA+IE=
|
||||
github.com/IBM/sarama v1.46.3/go.mod h1:GTUYiF9DMOZVe3FwyGT+dtSPceGFIgA+sPc5u6CBwko=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
@@ -79,6 +85,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/ardanlabs/conf/v3 v3.8.0 h1:Mvv2wZJz8tIl705m5BU3ZRCP1V6TKY6qebA8i4sykrY=
|
||||
github.com/ardanlabs/conf/v3 v3.8.0/go.mod h1:XlL9P0quWP4m1weOVFmlezabinbZLI05niDof/+Ochk=
|
||||
github.com/ardanlabs/conf/v3 v3.9.0 h1:aRBYHeD39/OkuaEXYIEoi4wvF3OnS7jUAPxXyLfEu20=
|
||||
github.com/ardanlabs/conf/v3 v3.9.0/go.mod h1:XlL9P0quWP4m1weOVFmlezabinbZLI05niDof/+Ochk=
|
||||
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
|
||||
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
|
||||
@@ -151,6 +159,8 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
@@ -172,6 +182,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gen2brain/avif v0.4.4 h1:Ga/ss7qcWWQm2bxFpnjYjhJsNfZrWs5RsyklgFjKRSE=
|
||||
github.com/gen2brain/avif v0.4.4/go.mod h1:/XCaJcjZraQwKVhpu9aEd9aLOssYOawLvhMBtmHVGqk=
|
||||
github.com/gen2brain/heic v0.4.5 h1:Cq3hPu6wwlTJNv2t48ro3oWje54h82Q5pALeCBNgaSk=
|
||||
@@ -182,8 +194,12 @@ github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg=
|
||||
github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -195,12 +211,34 @@ github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNP
|
||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
|
||||
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonpointer v0.22.2 h1:JDQEe4B9j6K3tQ7HQQTZfjR59IURhjjLxet2FB4KHyg=
|
||||
github.com/go-openapi/jsonpointer v0.22.2/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
|
||||
github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=
|
||||
github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8=
|
||||
github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo=
|
||||
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
|
||||
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
|
||||
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
|
||||
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -209,6 +247,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
@@ -248,6 +288,8 @@ github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||
@@ -294,6 +336,8 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -310,6 +354,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
@@ -331,14 +377,20 @@ github.com/nats-io/nats-server/v2 v2.9.23 h1:6Wj6H6QpP9FMlpCyWUaNu2yeZ/qGj+mdRkZ
|
||||
github.com/nats-io/nats-server/v2 v2.9.23/go.mod h1:wEjrEy9vnqIGE4Pqz4/c75v9Pmaq7My2IgFmnykc4C0=
|
||||
github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
|
||||
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olahol/melody v1.3.0 h1:n7UlKiQnxVrgxKoM0d7usZiN+Z0y2lVENtYLgKtXS6s=
|
||||
github.com/olahol/melody v1.3.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||
github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI=
|
||||
github.com/olahol/melody v1.4.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
@@ -358,8 +410,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM=
|
||||
github.com/pressly/goose/v3 v3.24.3/go.mod h1:v9zYL4xdViLHCUUJh/mhjnm6JrK7Eul8AS93IxiZM4E=
|
||||
github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM=
|
||||
github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
||||
@@ -379,6 +435,8 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
||||
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
||||
github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA=
|
||||
github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
@@ -394,6 +452,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
||||
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
|
||||
@@ -402,8 +462,12 @@ github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/tinylib/msgp v1.5.0 h1:GWnqAE54wmnlFazjq2+vgr736Akg58iiHImh+kPY2pc=
|
||||
github.com/tinylib/msgp v1.5.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
@@ -433,6 +497,8 @@ go.balki.me/anyhttp v0.5.2 h1:et4tCDXLeXpWfMNvRKG7ojfrnlr3du7cEaG966MLSpA=
|
||||
go.balki.me/anyhttp v0.5.2/go.mod h1:JhfekOIjgVODoVqUCficjpIgmB3wwlB7jhN0eN2EZ/s=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9plR/HNjgGBrfcOlweA=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=
|
||||
@@ -441,20 +507,32 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/X
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
gocloud.dev v0.43.0 h1:aW3eq4RMyehbJ54PMsh4hsp7iX8cO/98ZRzJJOzN/5M=
|
||||
gocloud.dev v0.43.0/go.mod h1:eD8rkg7LhKUHrzkEdLTZ+Ty/vgPHPCd+yMQdfelQVu4=
|
||||
gocloud.dev/pubsub/kafkapubsub v0.43.0 h1:Kgwi0na69W3RgxEffEkdrMhox6A3Q0gajoJtjHGVr/s=
|
||||
@@ -471,16 +549,24 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
|
||||
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
|
||||
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
||||
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -493,8 +579,12 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -502,6 +592,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -519,6 +611,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -534,8 +628,12 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
@@ -544,22 +642,34 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
|
||||
google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
|
||||
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
|
||||
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
|
||||
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs=
|
||||
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 h1:iOye66xuaAK0WnkPuhQPUFy8eJcmwUXqGGP3om6IxX8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79/go.mod h1:HKJDgKsFUnv5VAGeQjz8kxcgDP0HoE0iZNp0OdZNlhE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -572,16 +682,21 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
|
||||
modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/fileutil v1.3.15 h1:rJAXTP6ilMW/1+kzDiqmBlHLWszheUFXIyGQIAvjJpY=
|
||||
modernc.org/fileutil v1.3.15/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.7 h1:rjhZ8OSCybKWxS1CJr0hikpEi6Vg+944Ouyrd+bQsoY=
|
||||
modernc.org/libc v1.66.7/go.mod h1:ln6tbWX0NH+mzApEoDRvilBvAWFt1HX7AUA4VDdVDPM=
|
||||
modernc.org/libc v1.67.0 h1:QzL4IrKab2OFmxA3/vRYl0tLXrIamwrhD6CKD4WBVjQ=
|
||||
modernc.org/libc v1.67.0/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -592,6 +707,8 @@ modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/sqlite v1.40.0 h1:bNWEDlYhNPAUdUdBzjAvn8icAs/2gaKlj4vM+tQ6KdQ=
|
||||
modernc.org/sqlite v1.40.0/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -50,6 +50,7 @@ func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename st
|
||||
_, err = svc.repo.Attachments.Create(ctx, itemID, repo.ItemCreateAttachment{Title: filename, Content: file}, attachmentType, primary)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to create attachment")
|
||||
return repo.ItemOut{}, err
|
||||
}
|
||||
|
||||
return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||
)
|
||||
|
||||
func TestItemService_AddAttachment(t *testing.T) {
|
||||
@@ -60,3 +61,53 @@ func TestItemService_AddAttachment(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, contents, string(bts))
|
||||
}
|
||||
|
||||
func TestItemService_AddAttachment_InvalidStorage(t *testing.T) {
|
||||
// Create a service with an invalid storage path to simulate the issue
|
||||
svc := &ItemService{
|
||||
repo: tRepos,
|
||||
filepath: "/nonexistent/path/that/should/not/exist",
|
||||
}
|
||||
|
||||
// Create a temporary repo with invalid storage config
|
||||
invalidRepos := repo.New(tClient, tbus, config.Storage{
|
||||
PrefixPath: "/",
|
||||
ConnString: "file:///nonexistent/directory/that/does/not/exist",
|
||||
}, "mem://{{ .Topic }}", config.Thumbnail{
|
||||
Enabled: false,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
})
|
||||
|
||||
svc.repo = invalidRepos
|
||||
|
||||
loc, err := invalidRepos.Locations.Create(context.Background(), tGroup.ID, repo.LocationCreate{
|
||||
Description: "test",
|
||||
Name: "test-invalid",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, loc)
|
||||
|
||||
itmC := repo.ItemCreate{
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(10),
|
||||
LocationID: loc.ID,
|
||||
}
|
||||
|
||||
itm, err := invalidRepos.Items.Create(context.Background(), tGroup.ID, itmC)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, itm)
|
||||
t.Cleanup(func() {
|
||||
err := invalidRepos.Items.Delete(context.Background(), itm.ID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
contents := fk.Str(1000)
|
||||
reader := strings.NewReader(contents)
|
||||
|
||||
// Attempt to add attachment with invalid storage - should return an error
|
||||
_, err = svc.AttachmentAdd(tCtx, itm.ID, "testfile.txt", "attachment", false, reader)
|
||||
|
||||
// This should return an error now (after the fix)
|
||||
assert.Error(t, err, "AttachmentAdd should return an error when storage is invalid")
|
||||
}
|
||||
|
||||
@@ -218,9 +218,8 @@ func (r *AttachmentRepo) Create(ctx context.Context, itemID uuid.UUID, doc ItemC
|
||||
// Upload the file to the storage bucket
|
||||
path, err := r.UploadFile(ctx, itemGroup, doc)
|
||||
if err != nil {
|
||||
err := tx.Rollback()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
return nil, rollbackErr
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
)
|
||||
|
||||
type ItemsRepository struct {
|
||||
db *ent.Client
|
||||
bus *eventbus.EventBus
|
||||
db *ent.Client
|
||||
bus *eventbus.EventBus
|
||||
attachments *AttachmentRepo
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -318,8 +319,13 @@ func (e *ItemsRepository) publishMutationEvent(gid uuid.UUID) {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) getOne(ctx context.Context, where ...predicate.Item) (ItemOut, error) {
|
||||
q := e.db.Item.Query().Where(where...)
|
||||
func (e *ItemsRepository) getOneTx(ctx context.Context, tx *ent.Tx, where ...predicate.Item) (ItemOut, error) {
|
||||
var q *ent.ItemQuery
|
||||
if tx != nil {
|
||||
q = tx.Item.Query().Where(where...)
|
||||
} else {
|
||||
q = e.db.Item.Query().Where(where...)
|
||||
}
|
||||
|
||||
return mapItemOutErr(q.
|
||||
WithFields().
|
||||
@@ -332,6 +338,10 @@ func (e *ItemsRepository) getOne(ctx context.Context, where ...predicate.Item) (
|
||||
)
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) getOne(ctx context.Context, where ...predicate.Item) (ItemOut, error) {
|
||||
return e.getOneTx(ctx, nil, where...)
|
||||
}
|
||||
|
||||
// GetOne returns a single item by ID. If the item does not exist, an error is returned.
|
||||
// See also: GetOneByGroup to ensure that the item belongs to a specific group.
|
||||
func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (ItemOut, error) {
|
||||
@@ -576,12 +586,21 @@ func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, gid uuid.UUID)
|
||||
return mapItemsSummaryErr(q.All(ctx))
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, gid uuid.UUID) (AssetID, error) {
|
||||
q := e.db.Item.Query().Where(
|
||||
item.HasGroupWith(group.ID(gid)),
|
||||
).Order(
|
||||
ent.Desc(item.FieldAssetID),
|
||||
).Limit(1)
|
||||
func (e *ItemsRepository) GetHighestAssetIDTx(ctx context.Context, tx *ent.Tx, gid uuid.UUID) (AssetID, error) {
|
||||
var q *ent.ItemQuery
|
||||
if tx != nil {
|
||||
q = tx.Item.Query().Where(
|
||||
item.HasGroupWith(group.ID(gid)),
|
||||
).Order(
|
||||
ent.Desc(item.FieldAssetID),
|
||||
).Limit(1)
|
||||
} else {
|
||||
q = e.db.Item.Query().Where(
|
||||
item.HasGroupWith(group.ID(gid)),
|
||||
).Order(
|
||||
ent.Desc(item.FieldAssetID),
|
||||
).Limit(1)
|
||||
}
|
||||
|
||||
result, err := q.First(ctx)
|
||||
if err != nil {
|
||||
@@ -594,6 +613,10 @@ func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, gid uuid.UUID)
|
||||
return AssetID(result.AssetID), nil
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, gid uuid.UUID) (AssetID, error) {
|
||||
return e.GetHighestAssetIDTx(ctx, nil, gid)
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) SetAssetID(ctx context.Context, gid uuid.UUID, id uuid.UUID, assetID AssetID) error {
|
||||
q := e.db.Item.Update().Where(
|
||||
item.HasGroupWith(group.ID(gid)),
|
||||
@@ -632,7 +655,32 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCr
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
err := e.db.Item.DeleteOneID(id).Exec(ctx)
|
||||
// Get the item with its group and attachments before deletion
|
||||
itm, err := e.db.Item.Query().
|
||||
Where(item.ID(id)).
|
||||
WithGroup().
|
||||
WithAttachments().
|
||||
Only(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the group ID for attachment deletion
|
||||
var gid uuid.UUID
|
||||
if itm.Edges.Group != nil {
|
||||
gid = itm.Edges.Group.ID
|
||||
}
|
||||
|
||||
// Delete all attachments (and their files) before deleting the item
|
||||
for _, att := range itm.Edges.Attachments {
|
||||
err := e.attachments.Delete(ctx, gid, id, att.ID)
|
||||
if err != nil {
|
||||
log.Err(err).Str("attachment_id", att.ID.String()).Msg("failed to delete attachment during item deletion")
|
||||
// Continue with other attachments even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
err = e.db.Item.DeleteOneID(id).Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -642,7 +690,28 @@ func (e *ItemsRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error {
|
||||
_, err := e.db.Item.
|
||||
// Get the item with its attachments before deletion
|
||||
itm, err := e.db.Item.Query().
|
||||
Where(
|
||||
item.ID(id),
|
||||
item.HasGroupWith(group.ID(gid)),
|
||||
).
|
||||
WithAttachments().
|
||||
Only(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all attachments (and their files) before deleting the item
|
||||
for _, att := range itm.Edges.Attachments {
|
||||
err := e.attachments.Delete(ctx, gid, id, att.ID)
|
||||
if err != nil {
|
||||
log.Err(err).Str("attachment_id", att.ID.String()).Msg("failed to delete attachment during item deletion")
|
||||
// Continue with other attachments even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
_, err = e.db.Item.
|
||||
Delete().
|
||||
Where(
|
||||
item.ID(id),
|
||||
@@ -1119,12 +1188,12 @@ func (e *ItemsRepository) Duplicate(ctx context.Context, gid, id uuid.UUID, opti
|
||||
}()
|
||||
|
||||
// Get the original item with all its data
|
||||
originalItem, err := e.getOne(ctx, item.ID(id), item.HasGroupWith(group.ID(gid)))
|
||||
originalItem, err := e.getOneTx(ctx, tx, item.ID(id), item.HasGroupWith(group.ID(gid)))
|
||||
if err != nil {
|
||||
return ItemOut{}, err
|
||||
}
|
||||
|
||||
nextAssetID, err := e.GetHighestAssetID(ctx, gid)
|
||||
nextAssetID, err := e.GetHighestAssetIDTx(ctx, tx, gid)
|
||||
if err != nil {
|
||||
return ItemOut{}, err
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ package repo
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sysadminsmedia/homebox/backend/pkgs/textutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/sysadminsmedia/homebox/backend/pkgs/textutils"
|
||||
)
|
||||
|
||||
func TestItemsRepository_AccentInsensitiveSearch(t *testing.T) {
|
||||
// Test cases for accent-insensitive search
|
||||
testCases := []struct {
|
||||
name string
|
||||
itemName string
|
||||
searchQuery string
|
||||
shouldMatch bool
|
||||
description string
|
||||
name string
|
||||
itemName string
|
||||
searchQuery string
|
||||
shouldMatch bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "Spanish accented item, search without accents",
|
||||
@@ -155,25 +155,25 @@ func TestItemsRepository_AccentInsensitiveSearch(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Test the normalization logic used in the repository
|
||||
normalizedSearch := textutils.NormalizeSearchQuery(tc.searchQuery)
|
||||
|
||||
|
||||
// This simulates what happens in the repository
|
||||
// The original search would find exact matches (case-insensitive)
|
||||
// The normalized search would find accent-insensitive matches
|
||||
|
||||
|
||||
// Test that our normalization works as expected
|
||||
if tc.shouldMatch {
|
||||
// If it should match, then either the original query should match
|
||||
// or the normalized query should match when applied to the stored data
|
||||
assert.NotEqual(t, "", normalizedSearch, "Normalized search should not be empty")
|
||||
|
||||
|
||||
// The key insight is that we're searching with both the original and normalized queries
|
||||
// So "electrónica" will be found when searching for "electronica" because:
|
||||
// 1. Original search: "electronica" doesn't match "electrónica"
|
||||
// 2. Normalized search: "electronica" matches the normalized version
|
||||
t.Logf("✓ %s: Item '%s' should be found with search '%s' (normalized: '%s')",
|
||||
t.Logf("✓ %s: Item '%s' should be found with search '%s' (normalized: '%s')",
|
||||
tc.description, tc.itemName, tc.searchQuery, normalizedSearch)
|
||||
} else {
|
||||
t.Logf("✗ %s: Item '%s' should NOT be found with search '%s' (normalized: '%s')",
|
||||
t.Logf("✗ %s: Item '%s' should NOT be found with search '%s' (normalized: '%s')",
|
||||
tc.description, tc.itemName, tc.searchQuery, normalizedSearch)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,12 +2,14 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/types"
|
||||
)
|
||||
|
||||
@@ -317,3 +319,83 @@ func TestItemRepository_GetAllCustomFields(t *testing.T) {
|
||||
assert.ElementsMatch(t, values[:1], results)
|
||||
}
|
||||
}
|
||||
|
||||
func TestItemsRepository_DeleteWithAttachments(t *testing.T) {
|
||||
// Create an item with an attachment
|
||||
item := useItems(t, 1)[0]
|
||||
|
||||
// Add an attachment to the item
|
||||
attachment, err := tRepos.Attachments.Create(
|
||||
context.Background(),
|
||||
item.ID,
|
||||
ItemCreateAttachment{
|
||||
Title: "test-attachment.txt",
|
||||
Content: strings.NewReader("test content for attachment deletion"),
|
||||
},
|
||||
attachment.TypePhoto,
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, attachment)
|
||||
|
||||
// Verify the attachment exists
|
||||
retrievedAttachment, err := tRepos.Attachments.Get(context.Background(), tGroup.ID, attachment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, attachment.ID, retrievedAttachment.ID)
|
||||
|
||||
// Verify the attachment is linked to the item
|
||||
itemWithAttachments, err := tRepos.Items.GetOne(context.Background(), item.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, itemWithAttachments.Attachments, 1)
|
||||
assert.Equal(t, attachment.ID, itemWithAttachments.Attachments[0].ID)
|
||||
|
||||
// Delete the item
|
||||
err = tRepos.Items.Delete(context.Background(), item.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the item is deleted
|
||||
_, err = tRepos.Items.GetOne(context.Background(), item.ID)
|
||||
require.Error(t, err)
|
||||
|
||||
// Verify the attachment is also deleted
|
||||
_, err = tRepos.Attachments.Get(context.Background(), tGroup.ID, attachment.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestItemsRepository_DeleteByGroupWithAttachments(t *testing.T) {
|
||||
// Create an item with an attachment
|
||||
item := useItems(t, 1)[0]
|
||||
|
||||
// Add an attachment to the item
|
||||
attachment, err := tRepos.Attachments.Create(
|
||||
context.Background(),
|
||||
item.ID,
|
||||
ItemCreateAttachment{
|
||||
Title: "test-attachment-by-group.txt",
|
||||
Content: strings.NewReader("test content for attachment deletion by group"),
|
||||
},
|
||||
attachment.TypePhoto,
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, attachment)
|
||||
|
||||
// Verify the attachment exists
|
||||
retrievedAttachment, err := tRepos.Attachments.Get(context.Background(), tGroup.ID, attachment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, attachment.ID, retrievedAttachment.ID)
|
||||
|
||||
// Delete the item using DeleteByGroup
|
||||
err = tRepos.Items.DeleteByGroup(context.Background(), tGroup.ID, item.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the item is deleted
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item.ID)
|
||||
require.Error(t, err)
|
||||
|
||||
// Verify the attachment is also deleted
|
||||
_, err = tRepos.Attachments.Get(context.Background(), tGroup.ID, attachment.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,14 +21,15 @@ type AllRepos struct {
|
||||
}
|
||||
|
||||
func New(db *ent.Client, bus *eventbus.EventBus, storage config.Storage, pubSubConn string, thumbnail config.Thumbnail) *AllRepos {
|
||||
attachments := &AttachmentRepo{db, storage, pubSubConn, thumbnail}
|
||||
return &AllRepos{
|
||||
Users: &UserRepository{db},
|
||||
AuthTokens: &TokenRepository{db},
|
||||
Groups: NewGroupRepository(db),
|
||||
Locations: &LocationRepository{db, bus},
|
||||
Labels: &LabelRepository{db, bus},
|
||||
Items: &ItemsRepository{db, bus},
|
||||
Attachments: &AttachmentRepo{db, storage, pubSubConn, thumbnail},
|
||||
Items: &ItemsRepository{db, bus, attachments},
|
||||
Attachments: attachments,
|
||||
MaintEntry: &MaintenanceEntryRepository{db},
|
||||
Notifiers: NewNotifierRepository(db),
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ type LabelMakerConf struct {
|
||||
DynamicLength bool `yaml:"bool" conf:"default:true"`
|
||||
LabelServiceUrl *string `yaml:"label_service_url"`
|
||||
LabelServiceTimeout *time.Duration `yaml:"label_service_timeout"`
|
||||
RegularFontPath *string `yaml:"regular_font_path"`
|
||||
BoldFontPath *string `yaml:"bold_font_path"`
|
||||
}
|
||||
|
||||
type BarcodeAPIConf struct {
|
||||
|
||||
@@ -27,6 +27,13 @@ import (
|
||||
"golang.org/x/image/font/gofont/gomedium"
|
||||
)
|
||||
|
||||
type FontType int
|
||||
|
||||
const (
|
||||
FontTypeRegular FontType = iota
|
||||
FontTypeBold
|
||||
)
|
||||
|
||||
type GenerateParameters struct {
|
||||
Width int
|
||||
Height int
|
||||
@@ -140,6 +147,48 @@ func wrapText(text string, face font.Face, maxWidth int, maxHeight int, lineHeig
|
||||
return wrappedLines, ""
|
||||
}
|
||||
|
||||
func loadFont(cfg *config.Config, fontType FontType) (*truetype.Font, error) {
|
||||
var fontPath *string
|
||||
var fallbackData []byte
|
||||
|
||||
switch fontType {
|
||||
case FontTypeRegular:
|
||||
if cfg != nil && cfg.LabelMaker.RegularFontPath != nil {
|
||||
fontPath = cfg.LabelMaker.RegularFontPath
|
||||
}
|
||||
fallbackData = gomedium.TTF
|
||||
case FontTypeBold:
|
||||
if cfg != nil && cfg.LabelMaker.BoldFontPath != nil {
|
||||
fontPath = cfg.LabelMaker.BoldFontPath
|
||||
}
|
||||
fallbackData = gobold.TTF
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown font type: %d", fontType)
|
||||
}
|
||||
|
||||
if fontPath != nil && *fontPath != "" {
|
||||
data, err := os.ReadFile(*fontPath)
|
||||
if err != nil {
|
||||
log.Printf("Failed to load font from %s: %v, using fallback font", *fontPath, err)
|
||||
} else {
|
||||
font, err := truetype.Parse(data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse font from %s: %v, using fallback font", *fontPath, err)
|
||||
} else {
|
||||
log.Printf("Successfully loaded font from %s", *fontPath)
|
||||
return font, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
font, err := truetype.Parse(fallbackData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return font, nil
|
||||
}
|
||||
|
||||
func GenerateLabel(w io.Writer, params *GenerateParameters, cfg *config.Config) error {
|
||||
if err := params.Validate(); err != nil {
|
||||
return err
|
||||
@@ -165,12 +214,12 @@ func GenerateLabel(w io.Writer, params *GenerateParameters, cfg *config.Config)
|
||||
qr.DisableBorder = true
|
||||
qrImage := qr.Image(params.QrSize)
|
||||
|
||||
regularFont, err := truetype.Parse(gomedium.TTF)
|
||||
regularFont, err := loadFont(cfg, FontTypeRegular)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
boldFont, err := truetype.Parse(gobold.TTF)
|
||||
boldFont, err := loadFont(cfg, FontTypeBold)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
187
backend/pkgs/labelmaker/labelmaker_test.go
Normal file
187
backend/pkgs/labelmaker/labelmaker_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package labelmaker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||
"golang.org/x/image/font/gofont/gobold"
|
||||
"golang.org/x/image/font/gofont/gomedium"
|
||||
)
|
||||
|
||||
func TestLoadFont_WithNilConfig(t *testing.T) {
|
||||
font, err := loadFont(nil, FontTypeRegular)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
|
||||
font, err = loadFont(nil, FontTypeBold)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
}
|
||||
|
||||
func TestLoadFont_WithEmptyConfig(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
|
||||
font, err := loadFont(cfg, FontTypeRegular)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
|
||||
font, err = loadFont(cfg, FontTypeBold)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
}
|
||||
|
||||
func TestLoadFont_WithCustomFontPath(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
fontPath := filepath.Join(tempDir, "test-font.ttf")
|
||||
|
||||
err := os.WriteFile(fontPath, gomedium.TTF, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{
|
||||
LabelMaker: config.LabelMakerConf{
|
||||
RegularFontPath: &fontPath,
|
||||
},
|
||||
}
|
||||
|
||||
font, err := loadFont(cfg, FontTypeRegular)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
}
|
||||
|
||||
func TestLoadFont_WithNonExistentFontPath(t *testing.T) {
|
||||
nonExistentPath := "/non/existent/path/font.ttf"
|
||||
cfg := &config.Config{
|
||||
LabelMaker: config.LabelMakerConf{
|
||||
RegularFontPath: &nonExistentPath,
|
||||
},
|
||||
}
|
||||
|
||||
font, err := loadFont(cfg, FontTypeRegular)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
}
|
||||
|
||||
func TestLoadFont_UnknownFontType(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
|
||||
_, err := loadFont(cfg, FontType(999))
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown font type")
|
||||
}
|
||||
|
||||
func TestLoadFont_BoldFontWithCustomPath(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
fontPath := filepath.Join(tempDir, "test-bold-font.ttf")
|
||||
|
||||
err := os.WriteFile(fontPath, gobold.TTF, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{
|
||||
LabelMaker: config.LabelMakerConf{
|
||||
BoldFontPath: &fontPath,
|
||||
},
|
||||
}
|
||||
|
||||
font, err := loadFont(cfg, FontTypeBold)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
}
|
||||
|
||||
func TestLoadFont_EmptyStringPath(t *testing.T) {
|
||||
emptyPath := ""
|
||||
cfg := &config.Config{
|
||||
LabelMaker: config.LabelMakerConf{
|
||||
RegularFontPath: &emptyPath,
|
||||
},
|
||||
}
|
||||
|
||||
font, err := loadFont(cfg, FontTypeRegular)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, font)
|
||||
}
|
||||
|
||||
func TestLoadFont_CJKRendering(t *testing.T) {
|
||||
cjkFontPath := filepath.Join("testdata", "NotoSansKR-VF.ttf")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
fontPath string
|
||||
shouldHaveGlyph bool
|
||||
}{
|
||||
{
|
||||
name: "Korean with default font",
|
||||
text: "한글",
|
||||
fontPath: "",
|
||||
shouldHaveGlyph: false,
|
||||
},
|
||||
{
|
||||
name: "Chinese with default font",
|
||||
text: "中文",
|
||||
fontPath: "",
|
||||
shouldHaveGlyph: false,
|
||||
},
|
||||
{
|
||||
name: "Japanese with default font",
|
||||
text: "ひらがなカタカナ",
|
||||
fontPath: "",
|
||||
shouldHaveGlyph: false,
|
||||
},
|
||||
{
|
||||
name: "Korean with Noto Sans CJK",
|
||||
text: "한글",
|
||||
fontPath: cjkFontPath,
|
||||
shouldHaveGlyph: true,
|
||||
},
|
||||
{
|
||||
name: "Chinese with Noto Sans CJK",
|
||||
text: "中文",
|
||||
fontPath: cjkFontPath,
|
||||
shouldHaveGlyph: true,
|
||||
},
|
||||
{
|
||||
name: "Japanese with Noto Sans CJK",
|
||||
text: "ひらがなカタカナ",
|
||||
fontPath: cjkFontPath,
|
||||
shouldHaveGlyph: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var cfg *config.Config
|
||||
if tt.fontPath != "" {
|
||||
if _, err := os.Stat(tt.fontPath); os.IsNotExist(err) {
|
||||
t.Skipf("Font file not found: %s", tt.fontPath)
|
||||
}
|
||||
cfg = &config.Config{
|
||||
LabelMaker: config.LabelMakerConf{
|
||||
RegularFontPath: &tt.fontPath,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
font, err := loadFont(cfg, FontTypeRegular)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, font)
|
||||
|
||||
hasAllGlyphs := true
|
||||
for _, r := range tt.text {
|
||||
if font.Index(r) == 0 {
|
||||
hasAllGlyphs = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if tt.shouldHaveGlyph {
|
||||
assert.True(t, hasAllGlyphs, "Font should render %s characters", tt.name)
|
||||
} else {
|
||||
assert.False(t, hasAllGlyphs, "Default font should not render %s characters", tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
92
backend/pkgs/labelmaker/testdata/NotoSans-LICENSE
vendored
Normal file
92
backend/pkgs/labelmaker/testdata/NotoSans-LICENSE
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
backend/pkgs/labelmaker/testdata/NotoSansKR-VF.ttf
vendored
Normal file
BIN
backend/pkgs/labelmaker/testdata/NotoSansKR-VF.ttf
vendored
Normal file
Binary file not shown.
15
backend/pkgs/labelmaker/testdata/README.md
vendored
Normal file
15
backend/pkgs/labelmaker/testdata/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Test Data
|
||||
|
||||
This directory contains font files used only for testing purposes.
|
||||
|
||||
## Fonts
|
||||
|
||||
- **NotoSansKR-VF.ttf**: Noto Sans CJK Korean Variable Font
|
||||
- Used for testing CJK (Chinese, Japanese, Korean) character rendering
|
||||
- License: See `NotoSans-LICENSE` file
|
||||
|
||||
## Notes
|
||||
|
||||
- These fonts are **only used during testing** and are **not included in production builds**
|
||||
- Go's build system automatically excludes `testdata` directories from production builds
|
||||
- The fonts support rendering of Korean, Chinese, and Japanese characters
|
||||
@@ -3,7 +3,7 @@ services:
|
||||
image: homebox
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile.hardened
|
||||
dockerfile: ./Dockerfile
|
||||
args:
|
||||
- COMMIT=head
|
||||
- BUILD_TIME=0001-01-01T00:00:00Z
|
||||
@@ -11,7 +11,7 @@ services:
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/arm/v7
|
||||
- linux/arm
|
||||
environment:
|
||||
- HBOX_DEBUG=true
|
||||
- HBOX_LOGGER_LEVEL=-1
|
||||
|
||||
@@ -48,6 +48,8 @@ aside: false
|
||||
| HBOX_LABEL_MAKER_PRINT_COMMAND | | the command to use for printing labels. if empty, label printing is disabled. <span v-pre>`{{.FileName}}`</span> in the command will be replaced with the png filename of the label |
|
||||
| HBOX_LABEL_MAKER_DYNAMIC_LENGTH | true | allow label generation with open length. `HBOX_LABEL_MAKER_HEIGHT` is still used for layout and minimal height. If not used, long text may be cut off, but all labels have the same size. |
|
||||
| HBOX_LABEL_MAKER_ADDITIONAL_INFORMATION | | Additional information added to the label like name or phone number |
|
||||
| HBOX_LABEL_MAKER_REGULAR_FONT_PATH | | path to regular font file for label generation (e.g., `/fonts/NotoSansKR-Regular.ttf`). If not set, uses embedded font. Supports TTF format. |
|
||||
| HBOX_LABEL_MAKER_BOLD_FONT_PATH | | path to bold font file for label generation (e.g., `/fonts/NotoSansKR-Bold.ttf`). If not set, uses embedded font. Supports TTF format. |
|
||||
| HBOX_THUMBNAIL_ENABLED | true | enable thumbnail generation for images, supports PNG, JPEG, AVIF, WEBP, GIF file types |
|
||||
| HBOX_THUMBNAIL_WIDTH | 500 | width for generated thumbnails in pixels |
|
||||
| HBOX_THUMBNAIL_HEIGHT | 500 | height for generated thumbnails in pixels |
|
||||
@@ -192,6 +194,8 @@ OPTIONS
|
||||
--label-maker-print-command/$HBOX_LABEL_MAKER_PRINT_COMMAND <string>
|
||||
--label-maker-dynamic-length/$HBOX_LABEL_MAKER_DYNAMIC_LENGTH <bool> (default: true)
|
||||
--label-maker-additional-information/$HBOX_LABEL_MAKER_ADDITIONAL_INFORMATION <string>
|
||||
--label-maker-regular-font-path/$HBOX_LABEL_MAKER_REGULAR_FONT_PATH <string>
|
||||
--label-maker-bold-font-path/$HBOX_LABEL_MAKER_BOLD_FONT_PATH <string>
|
||||
--thumbnail-enabled/$HBOX_THUMBNAIL_ENABLED <bool> (default: true)
|
||||
--thumbnail-width/$HBOX_THUMBNAIL_WIDTH <int> (default: 500)
|
||||
--thumbnail-height/$HBOX_THUMBNAIL_HEIGHT <int> (default: 500)
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
# 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.
|
||||
::: danger Bounties Paused
|
||||
Do to issues with the bounty provider we were using, the bounty program is currently paused. We are working on a solution and will update this page when bounties are available again.
|
||||
|
||||
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)
|
||||
For more information, please check our [Blog post](https://sysadminsjournal.com/navigating-the-future-of-homeboxs-bounties/).
|
||||
:::
|
||||
|
||||
80
docs/en/custom-font-setup.md
Normal file
80
docs/en/custom-font-setup.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# External Font Support for Label Maker
|
||||
|
||||
Label maker supports external font files.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Docker/Podman Setup
|
||||
|
||||
1. **Download external fonts** (e.g., Noto Sans KR):
|
||||
- Download from [Google Fonts](https://fonts.google.com/noto/specimen/Noto+Sans+KR)
|
||||
- Or use the Variable Font from [GitHub](https://github.com/notofonts/noto-cjk)
|
||||
|
||||
2. **Create a fonts directory**:
|
||||
```bash
|
||||
mkdir -p ./fonts
|
||||
# Place your font files in this directory
|
||||
# e.g., NotoSansKR-VF.ttf
|
||||
```
|
||||
|
||||
3. **Mount the fonts directory and set environment variables**:
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
homebox:
|
||||
image: homebox:latest
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./fonts:/fonts:ro # Mount fonts directory as read-only
|
||||
environment:
|
||||
- HBOX_LABEL_MAKER_REGULAR_FONT_PATH=/fonts/NotoSansKR-VF.ttf
|
||||
- HBOX_LABEL_MAKER_BOLD_FONT_PATH=/fonts/NotoSansKR-VF.ttf
|
||||
ports:
|
||||
- 3100:7745
|
||||
```
|
||||
|
||||
Or with podman:
|
||||
```bash
|
||||
podman run -d \
|
||||
--name homebox \
|
||||
-p 3100:7745 \
|
||||
-v ./data:/data \
|
||||
-v ./fonts:/fonts:ro \
|
||||
-e HBOX_LABEL_MAKER_REGULAR_FONT_PATH=/fonts/NotoSansKR-VF.ttf \
|
||||
-e HBOX_LABEL_MAKER_BOLD_FONT_PATH=/fonts/NotoSansKR-VF.ttf \
|
||||
homebox:latest
|
||||
```
|
||||
|
||||
4. **Restart the container** and test label generation with Chinese, Japanese, Korean text!
|
||||
|
||||
## Supported Fonts
|
||||
|
||||
- **Format**: TTF (TrueType Font)
|
||||
- **Recommended Fonts**:
|
||||
- Noto Sans KR (Korean)
|
||||
- Noto Sans CJK (Chinese, Japanese, Korean)
|
||||
- Noto Sans SC (Simplified Chinese)
|
||||
- Noto Sans JP (Japanese)
|
||||
|
||||
## Fallback Behavior
|
||||
|
||||
1. **External font specified** → Tries to load from `HBOX_LABEL_MAKER_*_FONT_PATH`
|
||||
2. **External font fails or not specified** → Falls back to bundled Go fonts (Latin-only, **does not support CJK characters**)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Labels still show squares (□□□)
|
||||
- Check if the font file exists at the specified path
|
||||
- Verify the font file format (must be TTF, not OTF)
|
||||
- Check container logs: `podman logs homebox | grep -i font`
|
||||
|
||||
### Font file not found
|
||||
- Ensure the volume is correctly mounted
|
||||
- Check file permissions (font files should be readable)
|
||||
- Use absolute paths in environment variables
|
||||
|
||||
## Why External Fonts?
|
||||
|
||||
- **Smaller base image**: No need to embed large font files (~10MB per font)
|
||||
- **Flexibility**: Easily switch fonts without rebuilding the image
|
||||
- **Multi-language support**: Add support for any language by mounting appropriate fonts
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Content-Security-Policy: default-src 'self'; script-src 'report-sample' 'unsafe-inline' 'self' https://a.sysadmins.zone/js/embed.host.js https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015 https://unpkg.com/@stoplight/elements/web-components.min.js; style-src 'report-sample' 'unsafe-inline' 'self' https://unpkg.com; object-src 'none'; base-uri 'self'; connect-src 'self' https://raw.githubusercontent.com; font-src 'self'; frame-src 'self' https://a.sysadmins.zone; img-src 'self' data: http://translate.sysadminsmedia.com; manifest-src 'self'; media-src 'self'; worker-src 'none';
|
||||
Content-Security-Policy: default-src 'self'; script-src 'report-sample' 'unsafe-inline' 'self' https://a.sysadmins.zone/js/embed.host.js https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015 https://unpkg.com/@stoplight/elements/web-components.min.js; style-src 'report-sample' 'unsafe-inline' 'self' https://unpkg.com; object-src 'none'; base-uri 'self'; connect-src 'self' https://raw.githubusercontent.com https://demo.homebox.software; font-src 'self'; frame-src 'self' https://a.sysadmins.zone; img-src 'self' data: http://translate.sysadminsmedia.com; manifest-src 'self'; media-src 'self'; worker-src 'none';
|
||||
|
||||
@@ -1005,12 +1005,12 @@
|
||||
|
||||
.markdown :where(ul) {
|
||||
list-style: disc;
|
||||
margin-left: 2rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.markdown :where(ol) {
|
||||
list-style: decimal;
|
||||
margin-left: 2rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
/* Heading Styles */
|
||||
.markdown :where(h1) {
|
||||
|
||||
83
frontend/components/Form/MarkdownEditor.vue
Normal file
83
frontend/components/Form/MarkdownEditor.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import Markdown from "@/components/global/Markdown.vue";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string | null;
|
||||
label?: string | null;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
}>(),
|
||||
{
|
||||
modelValue: null,
|
||||
label: null,
|
||||
maxLength: -1,
|
||||
minLength: -1,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const local = ref(props.modelValue ?? "");
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
v => {
|
||||
if (v !== local.value) local.value = v ?? "";
|
||||
}
|
||||
);
|
||||
|
||||
watch(local, v => emit("update:modelValue", v === "" ? null : v));
|
||||
|
||||
const showPreview = ref(false);
|
||||
|
||||
const id = useId();
|
||||
|
||||
const isLengthInvalid = computed(() => {
|
||||
if (typeof local.value !== "string") return false;
|
||||
const len = local.value.length;
|
||||
const max = props.maxLength ?? -1;
|
||||
const min = props.minLength ?? -1;
|
||||
return (max !== -1 && len > max) || (min !== -1 && len < min);
|
||||
});
|
||||
|
||||
const lengthIndicator = computed(() => {
|
||||
if (typeof local.value !== "string") return "";
|
||||
const max = props.maxLength ?? -1;
|
||||
if (max !== -1) {
|
||||
return `${local.value.length}/${max}`;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="mb-2 grid grid-cols-1 items-center gap-2 md:grid-cols-4">
|
||||
<div class="min-w-0">
|
||||
<Label :for="id" class="flex min-w-0 items-center gap-2 px-1">
|
||||
<span class="truncate" :title="props.label ?? ''">{{ props.label }}</span>
|
||||
<span class="grow" />
|
||||
<span class="ml-2 text-sm" :class="{ 'text-destructive': isLengthInvalid }">{{ lengthIndicator }}</span>
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div class="col-span-1 flex items-center justify-start gap-2 md:col-span-3 md:justify-end">
|
||||
<label class="text-xs text-slate-500">{{ $t("global.preview") }}</label>
|
||||
<Checkbox v-model="showPreview" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-col gap-4">
|
||||
<Textarea :id="id" v-model="local" autosize class="resize-none" />
|
||||
|
||||
<div v-if="showPreview">
|
||||
<Markdown :source="local" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -71,7 +71,7 @@
|
||||
ref="fileInput"
|
||||
class="absolute left-0 top-0 size-full cursor-pointer opacity-0"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/gif,image/avif,image/webp;capture=camera"
|
||||
accept="image/png,image/jpeg,image/gif,image/avif,image/webp,android/force-camera-workaround"
|
||||
multiple
|
||||
@change="previewImage"
|
||||
/>
|
||||
|
||||
@@ -25,14 +25,13 @@
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="markdown text-wrap break-words" v-html="raw" />
|
||||
<div class="markdown prose text-wrap break-words" v-html="raw" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
word-wrap: break-word; /*Fix for long words going out of emelent bounds and issue #407 */
|
||||
overflow-wrap: break-word; /*Fix for long words going out of emelent bounds and issue #407 */
|
||||
white-space: pre-wrap; /*Fix for long words going out of emelent bounds and issue #407 */
|
||||
}
|
||||
.markdown {
|
||||
max-width: 100%;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { CurrenciesCurrency } from "~/lib/api/types/data-contracts";
|
||||
|
||||
export function validDate(dt: Date | string | null | undefined): boolean {
|
||||
if (!dt) {
|
||||
return false;
|
||||
@@ -42,7 +44,7 @@ function clampDecimals(currency: string, decimals: number): number {
|
||||
}
|
||||
|
||||
// Type guard to validate currency response shape with strict validation
|
||||
function isValidCurrencyItem(item: any): item is { code: string; decimals: number } {
|
||||
function isValidCurrencyItem(item: CurrenciesCurrency): boolean {
|
||||
if (
|
||||
typeof item !== "object" ||
|
||||
item === null ||
|
||||
|
||||
@@ -368,12 +368,12 @@
|
||||
]);
|
||||
|
||||
const labelStore = useLabelStore();
|
||||
labelStore.ensureAllLabelsFetched();
|
||||
|
||||
const locationStore = useLocationStore();
|
||||
locationStore.ensureLocationsFetched();
|
||||
|
||||
onMounted(() => {
|
||||
labelStore.refresh();
|
||||
locationStore.refreshChildren();
|
||||
locationStore.refreshParents();
|
||||
locationStore.refreshTree();
|
||||
});
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { factorRange, format, parse, zeroTime } from "./datelib";
|
||||
|
||||
describe("format", () => {
|
||||
test("should format a date as a string", () => {
|
||||
const date = new Date(2020, 1, 1);
|
||||
expect(format(date)).toBe("2020-02-01");
|
||||
});
|
||||
|
||||
test("should return the string if a string is passed in", () => {
|
||||
expect(format("2020-02-01")).toBe("2020-02-01");
|
||||
});
|
||||
});
|
||||
import { factorRange, parse, zeroTime } from "./datelib";
|
||||
|
||||
describe("zeroTime", () => {
|
||||
test("should zero out the time", () => {
|
||||
const date = new Date(2020, 1, 1, 12, 30, 30);
|
||||
const date = new Date(Date.UTC(2020, 1, 1, 12, 30, 30));
|
||||
const zeroed = zeroTime(date);
|
||||
expect(zeroed.getHours()).toBe(0);
|
||||
expect(zeroed.getMinutes()).toBe(0);
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import { addDays } from "date-fns";
|
||||
|
||||
/*
|
||||
* Formats a date as a string
|
||||
* */
|
||||
export function format(date: Date | string): string {
|
||||
if (typeof date === "string") {
|
||||
return date;
|
||||
}
|
||||
return date.toISOString().split("T")[0]!;
|
||||
}
|
||||
|
||||
export function zeroTime(date: Date): Date {
|
||||
return new Date(
|
||||
new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - date.getTimezoneOffset() * 60000
|
||||
);
|
||||
const result = new Date(date.getTime());
|
||||
result.setHours(0, 0, 0, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function factorRange(offset: number = 7): [Date, Date] {
|
||||
|
||||
@@ -279,7 +279,8 @@
|
||||
"updating": "Updating",
|
||||
"value": "Value",
|
||||
"version": "Version: { version }",
|
||||
"welcome": "Welcome, { username }"
|
||||
"welcome": "Welcome, { username }",
|
||||
"preview": "Preview"
|
||||
},
|
||||
"home": {
|
||||
"labels": "Labels",
|
||||
|
||||
@@ -9,74 +9,74 @@
|
||||
"lint:fix": "eslint --ext \".ts,.js,.vue\" --ignore-pattern \"components/ui\" . --fix",
|
||||
"lint:ci": "eslint --ext \".ts,.js,.vue\" --ignore-pattern \"components/ui\" . --max-warnings 1",
|
||||
"typecheck": "pnpm nuxi typecheck --noEmit",
|
||||
"test:ci": "TEST_SHUTDOWN_API_SERVER=true vitest --run --config ./test/vitest.config.ts",
|
||||
"test:ci": "TEST_SHUTDOWN_API_SERVER=true vitest --run --config ./test/vitest.config.ts --no-file-parallelism",
|
||||
"test:local": "TEST_SHUTDOWN_API_SERVER=false && vitest --run --config ./test/vitest.config.ts",
|
||||
"test:watch": " TEST_SHUTDOWN_API_SERVER=false vitest --config ./test/vitest.config.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.2",
|
||||
"@faker-js/faker": "^10.0.0",
|
||||
"@eslint/compat": "^1.4.1",
|
||||
"@faker-js/faker": "^10.1.0",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||
"@nuxt/eslint": "^1.9.0",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@nuxt/eslint": "^1.10.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@vite-pwa/nuxt": "^1.0.4",
|
||||
"@vue/runtime-core": "^3.5.20",
|
||||
"eslint": "^9.34.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@vite-pwa/nuxt": "^1.0.7",
|
||||
"@vue/runtime-core": "^3.5.24",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-tailwindcss": "^3.18.2",
|
||||
"globals": "^16.3.0",
|
||||
"globals": "^16.5.0",
|
||||
"h3": "^1.7.1",
|
||||
"intl-messageformat": "^10.7.16",
|
||||
"intl-messageformat": "^10.7.18",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"nuxt": "4.0.3",
|
||||
"nuxt": "4.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"shadcn-nuxt": "2.2.0",
|
||||
"typescript": "5.9.2",
|
||||
"unplugin-icons": "^22.2.0",
|
||||
"unplugin-icons": "^22.5.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vitest": "^3.2.4",
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-tsc": "3.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdit/plugin-img-size": "^0.22.2",
|
||||
"@mdit/plugin-img-size": "^0.22.3",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@pinia/nuxt": "^0.11.2",
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"@vueuse/core": "^13.8.0",
|
||||
"@vueuse/nuxt": "^13.8.0",
|
||||
"@vueuse/router": "^13.8.0",
|
||||
"@vuepic/vue-datepicker": "^11.0.3",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"@vueuse/nuxt": "^13.9.0",
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"@zxing/library": "^0.21.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"dompurify": "^3.2.6",
|
||||
"dompurify": "^3.3.0",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"h3": "^1.15.4",
|
||||
"http-proxy": "^1.18.1",
|
||||
"lucide-vue-next": "^0.542.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia": "^3.0.4",
|
||||
"postcss": "^8.5.6",
|
||||
"reka-ui": "^2.5.0",
|
||||
"semver": "^7.7.2",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"reka-ui": "^2.6.0",
|
||||
"semver": "^7.7.3",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul-vue": "^0.4.1",
|
||||
"vite": "^7.1.3",
|
||||
"vite": "^7.2.2",
|
||||
"vue": "3.5.20",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue-sonner": "^2.0.8"
|
||||
"vue-router": "^4.6.3",
|
||||
"vue-sonner": "^2.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
import MdiPackageVariant from "~icons/mdi/package-variant";
|
||||
import MdiPlus from "~icons/mdi/plus";
|
||||
import MdiMinus from "~icons/mdi/minus";
|
||||
import MdiContentCopy from "~icons/mdi/content-copy";
|
||||
import MdiDelete from "~icons/mdi/delete";
|
||||
import MdiPlusBoxMultipleOutline from "~icons/mdi/plus-box-multiple-outline";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -652,7 +652,7 @@
|
||||
<span class="hidden md:inline">{{ $t("global.create_subitem") }}</span>
|
||||
</Button>
|
||||
<Button class="w-9 md:w-auto" :aria-label="$t('global.duplicate')" @click="handleDuplicateClick">
|
||||
<MdiContentCopy />
|
||||
<MdiPlusBoxMultipleOutline />
|
||||
<span class="hidden md:inline">{{ $t("global.duplicate") }}</span>
|
||||
</Button>
|
||||
<Button variant="destructive" class="w-9 md:w-auto" :aria-label="$t('global.delete')" @click="deleteItem">
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { DialogID } from "~/components/ui/dialog-provider/utils";
|
||||
import FormTextField from "~/components/Form/TextField.vue";
|
||||
import FormTextArea from "~/components/Form/TextArea.vue";
|
||||
import MarkdownEditor from "~/components/Form/MarkdownEditor.vue";
|
||||
import FormDatePicker from "~/components/Form/DatePicker.vue";
|
||||
import FormCheckbox from "~/components/Form/Checkbox.vue";
|
||||
import LocationSelector from "~/components/Location/Selector.vue";
|
||||
@@ -147,7 +148,7 @@
|
||||
type DateKeys<T> = Extract<keyof T, keyof { [K in keyof T as T[K] extends Date | string ? K : never]: any }>;
|
||||
|
||||
type TextFormField = {
|
||||
type: "text" | "textarea";
|
||||
type: "text" | "textarea" | "markdown";
|
||||
label: string;
|
||||
ref: NonNullableStringKeys<ItemOut>;
|
||||
maxLength?: number;
|
||||
@@ -188,7 +189,7 @@
|
||||
ref: "quantity",
|
||||
},
|
||||
{
|
||||
type: "textarea",
|
||||
type: "markdown",
|
||||
label: "items.description",
|
||||
ref: "description",
|
||||
maxLength: 1000,
|
||||
@@ -212,7 +213,7 @@
|
||||
maxLength: 255,
|
||||
},
|
||||
{
|
||||
type: "textarea",
|
||||
type: "markdown",
|
||||
label: "items.notes",
|
||||
ref: "notes",
|
||||
maxLength: 1000,
|
||||
@@ -613,6 +614,13 @@
|
||||
:max-length="field.maxLength"
|
||||
:min-length="field.minLength"
|
||||
/>
|
||||
<MarkdownEditor
|
||||
v-else-if="field.type === 'markdown'"
|
||||
v-model="item[field.ref]"
|
||||
:label="$t(field.label)"
|
||||
:max-length="field.maxLength"
|
||||
:min-length="field.minLength"
|
||||
/>
|
||||
<FormTextField
|
||||
v-else-if="field.type === 'text'"
|
||||
v-model="item[field.ref]"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import BaseContainer from "@/components/Base/Container.vue";
|
||||
import SearchFilter from "~/components/Search/Filter.vue";
|
||||
import ItemViewSelectable from "~/components/Item/View/Selectable.vue";
|
||||
import type { LocationQueryRaw } from "vue-router";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -37,7 +38,41 @@
|
||||
const items = ref<ItemSummary[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
const page1 = useRouteQuery("page", 1);
|
||||
// Using useRouteQuery directly has two downsides
|
||||
// 1. It persists the default value in the query string
|
||||
// 2. The ref returned by useRouteQuery updates asynchronously after calling the setter.
|
||||
// This can cause unintuitive behaviors.
|
||||
// -> We copy query parameters into separate refs on page load and update the query explicitly via `router.push`.
|
||||
type QueryParamValue = string | string[] | number | boolean;
|
||||
type QueryRef = Ref<boolean | string | string[] | number, boolean | string | string[] | number>;
|
||||
const queryParamDefaultValues: Record<string, QueryParamValue> = {};
|
||||
function useOptionalRouteQuery(key: string, defaultValue: string): Ref<string>;
|
||||
function useOptionalRouteQuery(key: string, defaultValue: string[]): Ref<string[]>;
|
||||
function useOptionalRouteQuery(key: string, defaultValue: number): Ref<number>;
|
||||
function useOptionalRouteQuery(key: string, defaultValue: boolean): Ref<boolean>;
|
||||
function useOptionalRouteQuery(key: string, defaultValue: QueryParamValue): QueryRef {
|
||||
queryParamDefaultValues[key] = defaultValue;
|
||||
if (typeof defaultValue === "string") {
|
||||
const val = useRouteQuery(key, defaultValue);
|
||||
return ref(val.value);
|
||||
}
|
||||
if (Array.isArray(defaultValue)) {
|
||||
const val = useRouteQuery(key, defaultValue);
|
||||
return ref(val.value);
|
||||
}
|
||||
if (typeof defaultValue === "number") {
|
||||
const val = useRouteQuery(key, defaultValue);
|
||||
return ref(val.value);
|
||||
}
|
||||
if (typeof defaultValue === "boolean") {
|
||||
const val = useRouteQuery(key, defaultValue);
|
||||
return ref(val.value);
|
||||
}
|
||||
|
||||
throw Error(`Invalid query value type ${typeof defaultValue}`);
|
||||
}
|
||||
|
||||
const page1 = useOptionalRouteQuery("page", 1);
|
||||
|
||||
const page = computed({
|
||||
get: () => page1.value,
|
||||
@@ -46,14 +81,15 @@
|
||||
},
|
||||
});
|
||||
|
||||
const query = useRouteQuery("q", "");
|
||||
const advanced = useRouteQuery("advanced", false);
|
||||
const includeArchived = useRouteQuery("archived", false);
|
||||
const fieldSelector = useRouteQuery("fieldSelector", false);
|
||||
const negateLabels = useRouteQuery("negateLabels", false);
|
||||
const onlyWithoutPhoto = useRouteQuery("onlyWithoutPhoto", false);
|
||||
const onlyWithPhoto = useRouteQuery("onlyWithPhoto", false);
|
||||
const orderBy = useRouteQuery("orderBy", "name");
|
||||
const query = useOptionalRouteQuery("q", "");
|
||||
const includeArchived = useOptionalRouteQuery("archived", false);
|
||||
const fieldSelector = useOptionalRouteQuery("fieldSelector", false);
|
||||
const negateLabels = useOptionalRouteQuery("negateLabels", false);
|
||||
const onlyWithoutPhoto = useOptionalRouteQuery("onlyWithoutPhoto", false);
|
||||
const onlyWithPhoto = useOptionalRouteQuery("onlyWithPhoto", false);
|
||||
const orderBy = useOptionalRouteQuery("orderBy", "name");
|
||||
const qLoc = useOptionalRouteQuery("loc", []);
|
||||
const qLab = useOptionalRouteQuery("lab", []);
|
||||
|
||||
const preferences = useViewPreferences();
|
||||
const pageSize = computed(() => preferences.value.itemsPerTablePage);
|
||||
@@ -63,23 +99,14 @@
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
// Wait until locations and labels are loaded
|
||||
let maxRetry = 10;
|
||||
while (!labels.value || !locations.value) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
if (maxRetry-- < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
searchLocked.value = true;
|
||||
const qLoc = route.query.loc as string[];
|
||||
await Promise.all([locationsStore.ensureLocationsFetched(), labelStore.ensureAllLabelsFetched()]);
|
||||
if (qLoc) {
|
||||
selectedLocations.value = locations.value.filter(l => qLoc.includes(l.id));
|
||||
selectedLocations.value = locations.value.filter(l => qLoc.value.includes(l.id));
|
||||
}
|
||||
|
||||
const qLab = route.query.lab as string[];
|
||||
if (qLab) {
|
||||
selectedLabels.value = labels.value.filter(l => qLab.includes(l.id));
|
||||
selectedLabels.value = labels.value.filter(l => qLab.value.includes(l.id));
|
||||
}
|
||||
|
||||
queryParamsInitialized.value = true;
|
||||
@@ -111,7 +138,7 @@
|
||||
|
||||
const locationsStore = useLocationStore();
|
||||
|
||||
const locationFlatTree = await useFlatLocations();
|
||||
const locationFlatTree = useFlatLocations();
|
||||
|
||||
const locations = computed(() => locationsStore.allLocations);
|
||||
|
||||
@@ -181,19 +208,19 @@
|
||||
});
|
||||
|
||||
watch(onlyWithoutPhoto, (newV, oldV) => {
|
||||
if (newV && onlyWithPhoto) {
|
||||
if (newV && onlyWithPhoto.value) {
|
||||
// this triggers the watch on onlyWithPhoto
|
||||
onlyWithPhoto.value = false;
|
||||
}
|
||||
if (newV !== oldV) {
|
||||
} else if (newV !== oldV) {
|
||||
search();
|
||||
}
|
||||
});
|
||||
|
||||
watch(onlyWithPhoto, (newV, oldV) => {
|
||||
if (newV && onlyWithoutPhoto) {
|
||||
if (newV && onlyWithoutPhoto.value) {
|
||||
// this triggers the watch on onlyWithoutPhoto
|
||||
onlyWithoutPhoto.value = false;
|
||||
}
|
||||
if (newV !== oldV) {
|
||||
} else if (newV !== oldV) {
|
||||
search();
|
||||
}
|
||||
});
|
||||
@@ -220,29 +247,6 @@
|
||||
return data;
|
||||
}
|
||||
|
||||
watch(advanced, (v, lv) => {
|
||||
if (v === false && lv === true) {
|
||||
selectedLocations.value = [];
|
||||
selectedLabels.value = [];
|
||||
fieldTuples.value = [];
|
||||
|
||||
console.log("advanced", advanced.value);
|
||||
|
||||
router.push({
|
||||
query: {
|
||||
advanced: route.query.advanced,
|
||||
q: query.value,
|
||||
page: page.value,
|
||||
archived: includeArchived.value ? "true" : "false",
|
||||
negateLabels: negateLabels.value ? "true" : "false",
|
||||
onlyWithoutPhoto: onlyWithoutPhoto.value ? "true" : "false",
|
||||
onlyWithPhoto: onlyWithPhoto.value ? "true" : "false",
|
||||
orderBy: orderBy.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function search() {
|
||||
if (searchLocked.value) {
|
||||
return;
|
||||
@@ -258,6 +262,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
const push_query: Record<string, string | string[] | number | boolean | undefined> = {
|
||||
archived: includeArchived.value,
|
||||
fieldSelector: fieldSelector.value,
|
||||
negateLabels: negateLabels.value,
|
||||
onlyWithoutPhoto: onlyWithoutPhoto.value,
|
||||
onlyWithPhoto: onlyWithPhoto.value,
|
||||
orderBy: orderBy.value,
|
||||
page: page.value,
|
||||
q: query.value,
|
||||
loc: locIDs.value,
|
||||
lab: labIDs.value,
|
||||
fields: fields,
|
||||
};
|
||||
|
||||
for (const key in push_query) {
|
||||
const val = push_query[key];
|
||||
const defaultVal = queryParamDefaultValues[key];
|
||||
if (
|
||||
(Array.isArray(val) &&
|
||||
Array.isArray(defaultVal) &&
|
||||
val.length == defaultVal.length &&
|
||||
val.every(v => (defaultVal as string[]).includes(v))) ||
|
||||
val === queryParamDefaultValues[key]
|
||||
) {
|
||||
push_query[key] = undefined;
|
||||
}
|
||||
|
||||
// Empirically seen to be unnecessary but according to router.push types,
|
||||
// booleans are not supported. This might be more stable.
|
||||
if (typeof push_query[key] === "boolean") {
|
||||
push_query[key] = String(val);
|
||||
}
|
||||
}
|
||||
|
||||
await router.push({ query: push_query as LocationQueryRaw });
|
||||
|
||||
const { data, error } = await api.items.getAll({
|
||||
q: query.value || "",
|
||||
locations: locIDs.value,
|
||||
@@ -308,27 +348,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Push non-reactive query fields
|
||||
await router.push({
|
||||
query: {
|
||||
// Reactive
|
||||
advanced: "true",
|
||||
archived: includeArchived.value ? "true" : "false",
|
||||
fieldSelector: fieldSelector.value ? "true" : "false",
|
||||
negateLabels: negateLabels.value ? "true" : "false",
|
||||
onlyWithoutPhoto: onlyWithoutPhoto.value ? "true" : "false",
|
||||
onlyWithPhoto: onlyWithPhoto.value ? "true" : "false",
|
||||
orderBy: orderBy.value,
|
||||
page: page.value,
|
||||
q: query.value,
|
||||
|
||||
// Non-reactive
|
||||
loc: locIDs.value,
|
||||
lab: labIDs.value,
|
||||
fields,
|
||||
},
|
||||
});
|
||||
|
||||
// Reset Pagination
|
||||
page.value = 1;
|
||||
|
||||
@@ -345,19 +364,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
await router.push({
|
||||
query: {
|
||||
archived: "false",
|
||||
fieldSelector: "false",
|
||||
page: 1,
|
||||
orderBy: "name",
|
||||
q: "",
|
||||
loc: [],
|
||||
lab: [],
|
||||
fields,
|
||||
},
|
||||
});
|
||||
|
||||
await search();
|
||||
}
|
||||
|
||||
|
||||
5842
frontend/pnpm-lock.yaml
generated
5842
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ export const useLabelStore = defineStore("labels", {
|
||||
state: () => ({
|
||||
allLabels: null as LabelOut[] | null,
|
||||
client: useUserApi(),
|
||||
refreshAllLabelsPromise: null as Promise<void> | null,
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
@@ -13,19 +14,20 @@ export const useLabelStore = defineStore("labels", {
|
||||
* response.
|
||||
*/
|
||||
labels(state): LabelOut[] {
|
||||
if (state.allLabels === null) {
|
||||
this.client.labels.getAll().then(result => {
|
||||
if (result.error) {
|
||||
console.error(result.error);
|
||||
}
|
||||
|
||||
this.allLabels = result.data;
|
||||
});
|
||||
}
|
||||
return state.allLabels ?? [];
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async ensureAllLabelsFetched() {
|
||||
if (this.allLabels !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.refreshAllLabelsPromise === null) {
|
||||
this.refreshAllLabelsPromise = this.refresh().then(() => {});
|
||||
}
|
||||
await this.refreshAllLabelsPromise;
|
||||
},
|
||||
async refresh() {
|
||||
const result = await this.client.labels.getAll();
|
||||
if (result.error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ export const useLocationStore = defineStore("locations", {
|
||||
Locations: null as LocationOutCount[] | null,
|
||||
client: useUserApi(),
|
||||
tree: null as TreeItem[] | null,
|
||||
refreshLocationsPromise: null as Promise<void> | null,
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
@@ -29,20 +30,20 @@ export const useLocationStore = defineStore("locations", {
|
||||
return state.parents ?? [];
|
||||
},
|
||||
allLocations(state): LocationOutCount[] {
|
||||
if (state.Locations === null) {
|
||||
this.client.locations.getAll({ filterChildren: false }).then(result => {
|
||||
if (result.error) {
|
||||
console.error(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.Locations = result.data;
|
||||
});
|
||||
}
|
||||
return state.Locations ?? [];
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async ensureLocationsFetched() {
|
||||
if (this.Locations !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.refreshLocationsPromise === null) {
|
||||
this.refreshLocationsPromise = this.refreshChildren().then(() => {});
|
||||
}
|
||||
await this.refreshLocationsPromise;
|
||||
},
|
||||
async refreshParents(): ReturnType<LocationsApi["getAll"]> {
|
||||
const result = await this.client.locations.getAll({ filterChildren: true });
|
||||
if (result.error) {
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
"author": "",
|
||||
"license": "AGPLv3",
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.3"
|
||||
"vitepress": "^1.6.4"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0",
|
||||
"dependencies": {
|
||||
"nuxt": "^4.0.0"
|
||||
"nuxt": "^4.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
5024
pnpm-lock.yaml
generated
5024
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user