Compare commits

...

23 Commits

Author SHA1 Message Date
Matthew Kilgore
e501d769da Upgrade frontend and doc dependencies 2025-11-16 16:51:16 -05:00
Matthew Kilgore
7d0e05dc5d Update go dependencies 2025-11-16 16:48:38 -05:00
Matt
81233e2999 Attempt to revert NodeJS so ARM 32bit builds work again (#1081)
* Attempt to revert NodeJS so ARM 32bit builds work again

* Rollback even further
2025-11-16 16:05:15 -05:00
tonyaellie
415c3238ae fix: android image capture for item create 2025-11-01 15:25:42 +00:00
Tonya
b3153cc971 Revert "Set single connection pool for sqlite3 (#1039)" (#1061)
This reverts commit 8a90b9c133.
2025-10-22 17:32:00 +00:00
Tonya
0801df9961 fix: use tx for duplicate (#1059) 2025-10-21 20:58:06 +01:00
Benjamin Wolff
2bdd085289 Item search query parameter modernisation (#1040)
* await labels and locations properly

* update query params with every search

* don't persist default settings in query params

* conceptualize optional parameters

* add run script for development

* lint

* consider typescript

* remove run.sh

* capitalize QueryParamValue

* make query parameter updates predictable

This reverts commit 5c0c48cea5.

* capitalize typename again

---------

Co-authored-by: Benji <benji@DG-SM-7059.local>
Co-authored-by: Benji <benji@mac.home.internal>
Co-authored-by: Benji <benji@dg-sm-7059.home.internal>
2025-10-21 19:40:46 +01:00
zebrapurring
c30cac4489 chore: update icon for button to duplicate items (#1050)
Co-authored-by: zebrapurring <>
2025-10-21 17:20:35 +00:00
Copilot
397a1c6f3e Fix: Return error to UI when attachment upload fails due to storage misconfiguration (#1045)
* Initial plan

* Fix attachment upload error handling to return errors to UI

Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>

* Final verification: All tests pass and code builds successfully

Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
2025-10-11 08:55:15 -04:00
Copilot
05a392346f Fix item deletion to properly clean up attachment files from storage (#1046)
* Initial plan

* Fix item deletion to properly clean up attachment files

Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
2025-10-11 08:55:02 -04:00
Tonya
28c3e102a2 feat: add a markdown preview for description and notes (#1043)
* feat: add a markdown preview for description and notes

* feat: add char count for md
2025-10-10 12:37:57 +00:00
zebrapurring
116e39531b Fix failing tests (#1009)
* chore: ignore all .data directories

* fix: date locale for unit tests

* test: disable parallelism to prevent database locks

* chore: fix lint errors

* chore: remove unused function

---------

Co-authored-by: zebrapurring <>
Co-authored-by: Tonya <tonya@tokia.dev>
2025-10-09 11:51:51 +00:00
rienkim
8a90b9c133 Set single connection pool for sqlite3 (#1039) 2025-10-08 14:58:29 -04:00
rienkim
ef52009f57 Feat/Added label maker custom font (#1038)
* Add label maker font config

* Add document for label maker font config

* Add test for custom font

* Fix custom font setup documentation

- Fallback font is gofont which don't support CJK characters

* Fix golangci-lint error

* Update custom-font-setup.md

* Fix typo
2025-10-08 14:49:22 -04:00
dependabot[bot]
76154263e0 Bump the npm_and_yarn group across 2 directories with 1 update (#1032)
Bumps the npm_and_yarn group with 1 update in the / directory: [nuxt](https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt).
Bumps the npm_and_yarn group with 1 update in the /frontend directory: [nuxt](https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt).


Updates `nuxt` from 4.0.3 to 4.1.0
- [Release notes](https://github.com/nuxt/nuxt/releases)
- [Commits](https://github.com/nuxt/nuxt/commits/v4.1.0/packages/nuxt)

Updates `nuxt` from 4.0.3 to 4.1.0
- [Release notes](https://github.com/nuxt/nuxt/releases)
- [Commits](https://github.com/nuxt/nuxt/commits/v4.1.0/packages/nuxt)

---
updated-dependencies:
- dependency-name: nuxt
  dependency-version: 4.1.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: nuxt
  dependency-version: 4.1.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-30 20:20:18 -04:00
Matthew Kilgore
108194e7fd Merge remote-tracking branch 'origin/main' 2025-09-29 19:21:19 -04:00
Matthew Kilgore
bf845ae0f7 Update bounty page 2025-09-29 19:21:05 -04:00
dependabot[bot]
9be6a8c888 Bump the npm_and_yarn group across 2 directories with 1 update (#1001)
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /frontend directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 5.4.18 to 5.4.20
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.20/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.20/packages/vite)

Updates `vite` from 7.1.3 to 7.1.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.20/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.20/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.20
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 7.1.5
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 12:07:33 -04:00
Alan Mooiman
38a987676e Fix frontend CI (#1028) 2025-09-29 08:18:43 -04:00
Matthew Kilgore
1f746efe27 Hide try it again (other issues) 2025-09-26 21:58:17 -04:00
Matthew Kilgore
d57bf8834b Fix CSP header 2025-09-26 21:45:57 -04:00
Matthew Kilgore
cb2c58c3f4 Merge remote-tracking branch 'origin/main' 2025-09-26 21:34:45 -04:00
Matthew Kilgore
7b3cf0453e Support TryIt Function in API Docs 2025-09-26 21:34:34 -04:00
43 changed files with 5493 additions and 6768 deletions

2
.gitignore vendored
View File

@@ -60,7 +60,7 @@ backend/app/api/static/public/*
backend/api
docs/.vitepress/cache/
/.data/
.data/
# Playwright
frontend/test-results/

View File

@@ -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": {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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)

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
})

View File

@@ -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)
}

View File

@@ -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),
}

View File

@@ -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 {

View File

@@ -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
}

View 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)
}
})
}
}

View 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.

Binary file not shown.

View 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

View File

@@ -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

View File

@@ -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)

View File

@@ -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/).
:::

View 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

View File

@@ -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';

View File

@@ -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) {

View 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>

View File

@@ -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"
/>

View File

@@ -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%;

View File

@@ -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 ||

View File

@@ -368,12 +368,12 @@
]);
const labelStore = useLabelStore();
labelStore.ensureAllLabelsFetched();
const locationStore = useLocationStore();
locationStore.ensureLocationsFetched();
onMounted(() => {
labelStore.refresh();
locationStore.refreshChildren();
locationStore.refreshParents();
locationStore.refreshTree();
});

View File

@@ -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);

View File

@@ -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] {

View File

@@ -279,7 +279,8 @@
"updating": "Updating",
"value": "Value",
"version": "Version: { version }",
"welcome": "Welcome, { username }"
"welcome": "Welcome, { username }",
"preview": "Preview"
},
"home": {
"labels": "Labels",

View File

@@ -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"
}
}

View File

@@ -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">

View File

@@ -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]"

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

File diff suppressed because it is too large Load Diff