diff --git a/.github/workflows/docker-publish-rootless.yaml b/.github/workflows/docker-publish-rootless.yaml
index 21839c54..db468c1f 100644
--- a/.github/workflows/docker-publish-rootless.yaml
+++ b/.github/workflows/docker-publish-rootless.yaml
@@ -51,6 +51,7 @@ jobs:
- linux/amd64
- linux/arm64
- linux/arm/v7
+ - linux/riscv64
steps:
- name: Enable Debug Logs
diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml
index 0613a063..0a238fba 100644
--- a/.github/workflows/docker-publish.yaml
+++ b/.github/workflows/docker-publish.yaml
@@ -51,6 +51,7 @@ jobs:
- linux/amd64
- linux/arm64
- linux/arm/v7
+ - linux/riscv64
steps:
- name: Checkout repository
diff --git a/.github/workflows/partial-backend.yaml b/.github/workflows/partial-backend.yaml
index b8d7064f..511283ae 100644
--- a/.github/workflows/partial-backend.yaml
+++ b/.github/workflows/partial-backend.yaml
@@ -35,8 +35,3 @@ jobs:
- name: Test
run: task go:coverage
-
- - name: Validate OpenAPI definition
- uses: swaggerexpert/swagger-editor-validate@v1
- with:
- definition-file: backend/app/api/static/docs/swagger.json
diff --git a/Dockerfile b/Dockerfile
index 8b553d15..1d8b8114 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Node dependencies stage
-FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:lts-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 --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
WORKDIR /app
# Install pnpm globally again (it can reuse the cache if not changed)
@@ -22,7 +22,7 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build
# Go dependencies stage
-FROM public.ecr.aws/docker/library/golang:alpine AS builder-dependencies
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/golang:alpine AS builder-dependencies
WORKDIR /go/src/app
# Copy go.mod and go.sum for better caching
@@ -30,7 +30,9 @@ COPY ./backend/go.mod ./backend/go.sum ./
RUN go mod download
# Build API stage
-FROM public.ecr.aws/docker/library/golang:alpine AS builder
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/golang:alpine AS builder
+ARG TARGETOS
+ARG TARGETARCH
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
@@ -52,20 +54,26 @@ COPY --from=frontend-builder /app/.output/public ./app/api/static/public
# Use cache for Go build artifacts
RUN --mount=type=cache,target=/root/.cache/go-build \
- CGO_ENABLED=0 GOOS=linux go build \
- -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
- -o /go/bin/api \
- -v ./app/api/*.go
+ if [ "$TARGETARCH" = "arm" ] || [ "$TARGETARCH" = "riscv64" ]; \
+ then CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
+ -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
+ -tags nodynamic -o /go/bin/api -v ./app/api/*.go; \
+ else \
+ CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
+ -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
+ -o /go/bin/api -v ./app/api/*.go; \
+ fi
# Production stage
-FROM public.ecr.aws/docker/library/alpine:latest
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/alpine:latest
ENV HBOX_MODE=production
-ENV HBOX_STORAGE_CONN_STRING=file:///
+ENV HBOX_STORAGE_CONN_STRING=file:///?no_tmp_dir=true
ENV HBOX_STORAGE_PREFIX_PATH=data
ENV HBOX_DATABASE_SQLITE_PATH=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite
# Install necessary runtime dependencies
-RUN apk --no-cache add ca-certificates wget
+RUN apk --no-cache add ca-certificates wget && \
+ if [ "$TARGETARCH" != "arm" ] || [ "$TARGETARCH" != "riscv64" ]; then apk --no-cache add libwebp libavif; fi
# Create application directory and copy over built Go binary
RUN mkdir /app
diff --git a/Dockerfile.rootless b/Dockerfile.rootless
index b6fcbdb4..9ca4f610 100644
--- a/Dockerfile.rootless
+++ b/Dockerfile.rootless
@@ -1,5 +1,5 @@
# Node dependencies stage
-FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:lts-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 --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
WORKDIR /app
# Install pnpm globally again (it can reuse the cache if not changed)
@@ -22,7 +22,7 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build
# Go dependencies stage
-FROM public.ecr.aws/docker/library/golang:alpine AS builder-dependencies
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/golang:alpine AS builder-dependencies
WORKDIR /go/src/app
# Copy go.mod and go.sum for better caching
@@ -30,7 +30,7 @@ COPY ./backend/go.mod ./backend/go.sum ./
RUN go mod download
# Build API stage
-FROM public.ecr.aws/docker/library/golang:alpine AS builder
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
@@ -52,22 +52,29 @@ COPY --from=frontend-builder /app/.output/public ./app/api/static/public
# Use cache for Go build artifacts
RUN --mount=type=cache,target=/root/.cache/go-build \
- CGO_ENABLED=0 GOOS=linux go build \
- -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
- -o /go/bin/api \
- -v ./app/api/*.go
+ if [ "$TARGETARCH" = "arm" ] || [ "$TARGETARCH" = "riscv64" ]; \
+ then CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
+ -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
+ -tags nodynamic -o /go/bin/api -v ./app/api/*.go; \
+ else \
+ CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
+ -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
+ -o /go/bin/api -v ./app/api/*.go; \
+ fi
RUN mkdir /data
# Production stage
-FROM public.ecr.aws/docker/library/alpine:latest
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/alpine:latest
ENV HBOX_MODE=production
-ENV HBOX_STORAGE_CONN_STRING=file:///
+ENV HBOX_STORAGE_CONN_STRING=file:///?no_tmp_dir=true
ENV HBOX_STORAGE_PREFIX_PATH=data
ENV HBOX_DATABASE_SQLITE_PATH=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite
# Install necessary runtime dependencies
-RUN apk --no-cache add ca-certificates wget
+RUN apk --no-cache add ca-certificates wget && \
+ if [ "$TARGETARCH" != "arm" ] || [ "$TARGETARCH" != "riscv64" ]; then apk --no-cache add libwebp libavif; fi
+
# Create a nonroot user with UID/GID 65532
RUN addgroup -g 65532 nonroot && adduser -u 65532 -G nonroot -S nonroot
diff --git a/backend/.goreleaser.yaml b/backend/.goreleaser.yaml
index 625e722a..565f5ab7 100644
--- a/backend/.goreleaser.yaml
+++ b/backend/.goreleaser.yaml
@@ -19,11 +19,21 @@ builds:
- "386"
- arm
- arm64
+ - riscv64
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: "386"
+ tags:
+ - >-
+ {{- if eq .Arch "riscv64" }}nodynamic
+ {{- else if eq .Arch "arm" }}nodynamic
+ {{- else if eq .Arch "386" }}nodynamic
+ {{ end }}
+sboms:
+ - disable: false
+ artifacts: any
signs:
- cmd: cosign
@@ -54,7 +64,6 @@ archives:
release:
extra_files:
- glob: dist/*.sig
-
checksum:
name_template: 'checksums.txt'
snapshot:
diff --git a/backend/app/api/handlers/v1/v1_ctrl_actions.go b/backend/app/api/handlers/v1/v1_ctrl_actions.go
index a134e490..9f53f496 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_actions.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_actions.go
@@ -81,3 +81,16 @@ func (ctrl *V1Controller) HandleItemDateZeroOut() errchain.HandlerFunc {
func (ctrl *V1Controller) HandleSetPrimaryPhotos() errchain.HandlerFunc {
return actionHandlerFactory("ensure asset IDs", ctrl.repo.Items.SetPrimaryPhotos)
}
+
+// HandleCreateMissingThumbnails godoc
+//
+// @Summary Create Missing Thumbnails
+// @Description Creates thumbnails for items that are missing them
+// @Tags Actions
+// @Produce json
+// @Success 200 {object} ActionAmountResult
+// @Router /v1/actions/create-missing-thumbnails [Post]
+// @Security Bearer
+func (ctrl *V1Controller) HandleCreateMissingThumbnails() errchain.HandlerFunc {
+ return actionHandlerFactory("create missing thumbnails", ctrl.repo.Attachments.CreateMissingThumbnails)
+}
diff --git a/backend/app/api/handlers/v1/v1_ctrl_auth.go b/backend/app/api/handlers/v1/v1_ctrl_auth.go
index dce66340..64d556fe 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_auth.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_auth.go
@@ -79,15 +79,15 @@ type AuthProvider interface {
// HandleAuthLogin godoc
//
-// @Summary User Login
-// @Tags Authentication
-// @Accept x-www-form-urlencoded
-// @Accept application/json
-// @Param payload body LoginForm true "Login Data"
-// @Param provider query string false "auth provider"
-// @Produce json
-// @Success 200 {object} TokenResponse
-// @Router /v1/users/login [POST]
+// @Summary User Login
+// @Tags Authentication
+// @Accept x-www-form-urlencoded
+// @Accept application/json
+// @Param payload body LoginForm true "Login Data"
+// @Param provider query string false "auth provider"
+// @Produce json
+// @Success 200 {object} TokenResponse
+// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFunc {
if len(ps) == 0 {
panic("no auth providers provided")
diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go
index 7242aa54..0deecea8 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_items.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_items.go
@@ -296,14 +296,14 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc {
// HandleItemsImport godocs
//
-// @Summary Import Items
-// @Tags Items
-// @Accept multipart/form-data
-// @Produce json
-// @Success 204
-// @Param csv formData file true "Image to upload"
-// @Router /v1/items/import [Post]
-// @Security Bearer
+// @Summary Import Items
+// @Tags Items
+// @Accept multipart/form-data
+// @Produce json
+// @Success 204
+// @Param csv formData file true "Image to upload"
+// @Router /v1/items/import [Post]
+// @Security Bearer
func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
diff --git a/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go b/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go
index 04a9d9bc..b4953fff 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go
@@ -31,19 +31,19 @@ type (
// HandleItemAttachmentCreate godocs
//
-// @Summary Create Item Attachment
-// @Tags Items Attachments
-// @Accept multipart/form-data
-// @Produce json
-// @Param id path string true "Item ID"
-// @Param file formData file true "File attachment"
-// @Param type formData string false "Type of file"
-// @Param primary formData bool false "Is this the primary attachment"
-// @Param name formData string true "name of the file including extension"
-// @Success 200 {object} repo.ItemOut
-// @Failure 422 {object} validate.ErrorResponse
-// @Router /v1/items/{id}/attachments [POST]
-// @Security Bearer
+// @Summary Create Item Attachment
+// @Tags Items Attachments
+// @Accept multipart/form-data
+// @Produce json
+// @Param id path string true "Item ID"
+// @Param file formData file true "File attachment"
+// @Param type formData string false "Type of file"
+// @Param primary formData bool false "Is this the primary attachment"
+// @Param name formData string true "name of the file including extension"
+// @Success 200 {object} repo.ItemOut
+// @Failure 422 {object} validate.ErrorResponse
+// @Router /v1/items/{id}/attachments [POST]
+// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
diff --git a/backend/app/api/handlers/v1/v1_ctrl_labelmaker.go b/backend/app/api/handlers/v1/v1_ctrl_labelmaker.go
index ed4620a2..f3b1613b 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_labelmaker.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_labelmaker.go
@@ -35,14 +35,14 @@ func generateOrPrint(ctrl *V1Controller, w http.ResponseWriter, r *http.Request,
// HandleGetLocationLabel godoc
//
-// @Summary Get Location label
-// @Tags Locations
-// @Produce json
-// @Param id path string true "Location ID"
-// @Param print query bool false "Print this label, defaults to false"
-// @Success 200 {string} string "image/png"
-// @Router /v1/labelmaker/location/{id} [GET]
-// @Security Bearer
+// @Summary Get Location label
+// @Tags Locations
+// @Produce json
+// @Param id path string true "Location ID"
+// @Param print query bool false "Print this label, defaults to false"
+// @Success 200 {string} string "image/png"
+// @Router /v1/labelmaker/location/{id} [GET]
+// @Security Bearer
func (ctrl *V1Controller) HandleGetLocationLabel() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ID, err := adapters.RouteUUID(r, "id")
@@ -63,14 +63,14 @@ func (ctrl *V1Controller) HandleGetLocationLabel() errchain.HandlerFunc {
// HandleGetItemLabel godoc
//
-// @Summary Get Item label
-// @Tags Items
-// @Produce json
-// @Param id path string true "Item ID"
-// @Param print query bool false "Print this label, defaults to false"
-// @Success 200 {string} string "image/png"
-// @Router /v1/labelmaker/item/{id} [GET]
-// @Security Bearer
+// @Summary Get Item label
+// @Tags Items
+// @Produce json
+// @Param id path string true "Item ID"
+// @Param print query bool false "Print this label, defaults to false"
+// @Success 200 {string} string "image/png"
+// @Router /v1/labelmaker/item/{id} [GET]
+// @Security Bearer
func (ctrl *V1Controller) HandleGetItemLabel() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ID, err := adapters.RouteUUID(r, "id")
@@ -97,14 +97,14 @@ func (ctrl *V1Controller) HandleGetItemLabel() errchain.HandlerFunc {
// HandleGetAssetLabel godoc
//
-// @Summary Get Asset label
-// @Tags Items
-// @Produce json
-// @Param id path string true "Asset ID"
-// @Param print query bool false "Print this label, defaults to false"
-// @Success 200 {string} string "image/png"
-// @Router /v1/labelmaker/assets/{id} [GET]
-// @Security Bearer
+// @Summary Get Asset label
+// @Tags Items
+// @Produce json
+// @Param id path string true "Asset ID"
+// @Param print query bool false "Print this label, defaults to false"
+// @Success 200 {string} string "image/png"
+// @Router /v1/labelmaker/assets/{id} [GET]
+// @Security Bearer
func (ctrl *V1Controller) HandleGetAssetLabel() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
assetIDParam := chi.URLParam(r, "id")
diff --git a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go
index b981405b..b465b1c3 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go
@@ -12,14 +12,14 @@ import (
// HandleMaintenanceLogGet godoc
//
-// @Summary Get Maintenance Log
-// @Tags Item Maintenance
-// @Produce json
-// @Param id path string true "Item ID"
-// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
-// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
-// @Router /v1/items/{id}/maintenance [GET]
-// @Security Bearer
+// @Summary Get Maintenance Log
+// @Tags Item Maintenance
+// @Produce json
+// @Param id path string true "Item ID"
+// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
+// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
+// @Router /v1/items/{id}/maintenance [GET]
+// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, filters repo.MaintenanceFilters) ([]repo.MaintenanceEntryWithDetails, error) {
auth := services.NewContext(r.Context())
@@ -31,14 +31,14 @@ func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
// HandleMaintenanceEntryCreate godoc
//
-// @Summary Create Maintenance Entry
-// @Tags Item Maintenance
-// @Produce json
-// @Param id path string true "Item ID"
-// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
-// @Success 201 {object} repo.MaintenanceEntry
-// @Router /v1/items/{id}/maintenance [POST]
-// @Security Bearer
+// @Summary Create Maintenance Entry
+// @Tags Item Maintenance
+// @Produce json
+// @Param id path string true "Item ID"
+// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
+// @Success 201 {object} repo.MaintenanceEntry
+// @Router /v1/items/{id}/maintenance [POST]
+// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc {
fn := func(r *http.Request, itemID uuid.UUID, body repo.MaintenanceEntryCreate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
diff --git a/backend/app/api/handlers/v1/v1_ctrl_maintenance.go b/backend/app/api/handlers/v1/v1_ctrl_maintenance.go
index 64ba29cc..becab81b 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_maintenance.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_maintenance.go
@@ -30,14 +30,14 @@ func (ctrl *V1Controller) HandleMaintenanceGetAll() errchain.HandlerFunc {
// HandleMaintenanceEntryUpdate godoc
//
-// @Summary Update Maintenance Entry
-// @Tags Maintenance
-// @Produce json
-// @Param id path string true "Maintenance ID"
-// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
-// @Success 200 {object} repo.MaintenanceEntry
-// @Router /v1/maintenance/{id} [PUT]
-// @Security Bearer
+// @Summary Update Maintenance Entry
+// @Tags Maintenance
+// @Produce json
+// @Param id path string true "Maintenance ID"
+// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
+// @Success 200 {object} repo.MaintenanceEntry
+// @Router /v1/maintenance/{id} [PUT]
+// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
@@ -49,13 +49,13 @@ func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
// HandleMaintenanceEntryDelete godoc
//
-// @Summary Delete Maintenance Entry
-// @Tags Maintenance
-// @Produce json
-// @Param id path string true "Maintenance ID"
-// @Success 204
-// @Router /v1/maintenance/{id} [DELETE]
-// @Security Bearer
+// @Summary Delete Maintenance Entry
+// @Tags Maintenance
+// @Produce json
+// @Param id path string true "Maintenance ID"
+// @Success 204
+// @Router /v1/maintenance/{id} [DELETE]
+// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
diff --git a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go
index 16072be4..a119e498 100644
--- a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go
+++ b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go
@@ -83,13 +83,13 @@ func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc {
// HandlerNotifierTest godoc
//
-// @Summary Test Notifier
-// @Tags Notifiers
-// @Produce json
-// @Param url query string true "URL"
-// @Success 204
-// @Router /v1/notifiers/test [POST]
-// @Security Bearer
+// @Summary Test Notifier
+// @Tags Notifiers
+// @Produce json
+// @Param url query string true "URL"
+// @Success 204
+// @Router /v1/notifiers/test [POST]
+// @Security Bearer
func (ctrl *V1Controller) HandlerNotifierTest() errchain.HandlerFunc {
type body struct {
URL string `json:"url" validate:"required"`
diff --git a/backend/app/api/logger.go b/backend/app/api/logger.go
index 714cba44..cde5b108 100644
--- a/backend/app/api/logger.go
+++ b/backend/app/api/logger.go
@@ -18,7 +18,7 @@ func (a *app) setupLogger() {
}
level, err := zerolog.ParseLevel(a.conf.Log.Level)
- if err == nil {
+ if err != nil {
zerolog.SetGlobalLevel(level)
}
}
diff --git a/backend/app/api/main.go b/backend/app/api/main.go
index a7cae8d3..98f1ab07 100644
--- a/backend/app/api/main.go
+++ b/backend/app/api/main.go
@@ -4,6 +4,8 @@ import (
"bytes"
"context"
"fmt"
+ "github.com/google/uuid"
+ "github.com/sysadminsmedia/homebox/backend/pkgs/utils"
"net/http"
"os"
"path/filepath"
@@ -34,6 +36,15 @@ import (
_ "github.com/sysadminsmedia/homebox/backend/internal/data/migrations/postgres"
_ "github.com/sysadminsmedia/homebox/backend/internal/data/migrations/sqlite3"
_ "github.com/sysadminsmedia/homebox/backend/pkgs/cgofreesqlite"
+
+ "gocloud.dev/pubsub"
+ _ "gocloud.dev/pubsub/awssnssqs"
+ _ "gocloud.dev/pubsub/azuresb"
+ _ "gocloud.dev/pubsub/gcppubsub"
+ _ "gocloud.dev/pubsub/kafkapubsub"
+ _ "gocloud.dev/pubsub/mempubsub"
+ _ "gocloud.dev/pubsub/natspubsub"
+ _ "gocloud.dev/pubsub/rabbitpubsub"
)
var (
@@ -64,19 +75,19 @@ func validatePostgresSSLMode(sslMode string) bool {
return validModes[strings.ToLower(strings.TrimSpace(sslMode))]
}
-// @title Homebox API
-// @version 1.0
-// @description Track, Manage, and Organize your Things.
-// @contact.name Homebox Team
-// @contact.url https://discord.homebox.software
-// @host demo.homebox.software
-// @schemes https http
-// @BasePath /api
-// @securityDefinitions.apikey Bearer
-// @in header
-// @name Authorization
-// @description "Type 'Bearer TOKEN' to correctly set the API Key"
-// @externalDocs.url https://homebox.software/en/api
+// @title Homebox API
+// @version 1.0
+// @description Track, Manage, and Organize your Things.
+// @contact.name Homebox Team
+// @contact.url https://discord.homebox.software
+// @host demo.homebox.software
+// @schemes https http
+// @BasePath /api
+// @securityDefinitions.apikey Bearer
+// @in header
+// @name Authorization
+// @description "Type 'Bearer TOKEN' to correctly set the API Key"
+// @externalDocs.url https://homebox.software/en/api
func main() {
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
@@ -91,6 +102,7 @@ func main() {
}
}
+//nolint:gocyclo
func run(cfg *config.Config) error {
app := new(cfg)
app.setupLogger()
@@ -203,7 +215,7 @@ func run(cfg *config.Config) error {
app.bus = eventbus.New()
app.db = c
- app.repos = repo.New(c, app.bus, cfg.Storage)
+ app.repos = repo.New(c, app.bus, cfg.Storage, cfg.Database.PubSubConnString, cfg.Thumbnail)
app.services = services.New(
app.repos,
services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID),
@@ -297,6 +309,75 @@ func run(cfg *config.Config) error {
}
}))
+ runner.AddFunc("create-thumbnails-subscription", func(ctx context.Context) error {
+ pubsubString, err := utils.GenerateSubPubConn(cfg.Database.PubSubConnString, "thumbnails")
+ if err != nil {
+ log.Error().Err(err).Msg("failed to generate pubsub connection string")
+ return err
+ }
+ topic, err := pubsub.OpenTopic(ctx, pubsubString)
+ if err != nil {
+ return err
+ }
+ defer func(topic *pubsub.Topic, ctx context.Context) {
+ err := topic.Shutdown(ctx)
+ if err != nil {
+ log.Err(err).Msg("fail to shutdown pubsub topic")
+ }
+ }(topic, ctx)
+
+ subscription, err := pubsub.OpenSubscription(ctx, pubsubString)
+ if err != nil {
+ log.Err(err).Msg("failed to open pubsub topic")
+ return err
+ }
+ defer func(topic *pubsub.Subscription, ctx context.Context) {
+ err := topic.Shutdown(ctx)
+ if err != nil {
+ log.Err(err).Msg("fail to shutdown pubsub topic")
+ }
+ }(subscription, ctx)
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ msg, err := subscription.Receive(ctx)
+ log.Debug().Msg("received thumbnail generation request from pubsub topic")
+ if err != nil {
+ log.Err(err).Msg("failed to receive message from pubsub topic")
+ return err
+ }
+ groupId, err := uuid.Parse(msg.Metadata["group_id"])
+ if err != nil {
+ log.Error().
+ Err(err).
+ Str("group_id", msg.Metadata["group_id"]).
+ Msg("failed to parse group ID from message metadata")
+ msg.Nack()
+ return err
+ }
+ attachmentId, err := uuid.Parse(msg.Metadata["attachment_id"])
+ if err != nil {
+ log.Error().
+ Err(err).
+ Str("attachment_id", msg.Metadata["attachment_id"]).
+ Msg("failed to parse attachment ID from message metadata")
+ msg.Nack()
+ return err
+ }
+ err = app.repos.Attachments.CreateThumbnail(ctx, groupId, attachmentId, msg.Metadata["title"], msg.Metadata["path"])
+ if err != nil {
+ msg.Nack()
+ log.Err(err).Msg("failed to create thumbnail")
+ return err
+ }
+ msg.Ack()
+ }
+ }
+ })
+
if cfg.Options.GithubReleaseCheck {
runner.AddPlugin(NewTask("get-latest-github-release", time.Hour, func(ctx context.Context) {
log.Debug().Msg("running get latest github release")
@@ -327,6 +408,8 @@ func run(cfg *config.Config) error {
log.Info().Msgf("Debug server is running on %s:%s", cfg.Web.Host, cfg.Debug.Port)
return debugserver.ListenAndServe()
})
+ // Print the configuration to the console
+ cfg.Print()
}
return runner.Start(context.Background())
diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go
index af92f54d..5d7900e4 100644
--- a/backend/app/api/routes.go
+++ b/backend/app/api/routes.go
@@ -102,6 +102,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
r.Post("/actions/zero-item-time-fields", chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
r.Post("/actions/ensure-import-refs", chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
r.Post("/actions/set-primary-photos", chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
+ r.Post("/actions/create-missing-thumbnails", chain.ToHandlerFunc(v1Ctrl.HandleCreateMissingThumbnails(), userMW...))
r.Get("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
r.Post("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go
index 955a401a..2059e884 100644
--- a/backend/app/api/static/docs/docs.go
+++ b/backend/app/api/static/docs/docs.go
@@ -18,6 +18,31 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
+ "/v1/actions/create-missing-thumbnails": {
+ "post": {
+ "security": [
+ {
+ "Bearer": []
+ }
+ ],
+ "description": "Creates thumbnails for items that are missing them",
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Actions"
+ ],
+ "summary": "Create Missing Thumbnails",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/v1.ActionAmountResult"
+ }
+ }
+ }
+ }
+ },
"/v1/actions/ensure-asset-ids": {
"post": {
"security": [
@@ -2096,6 +2121,42 @@ const docTemplate = `{
}
},
"definitions": {
+ "attachment.Type": {
+ "type": "string",
+ "enum": [
+ "attachment",
+ "photo",
+ "manual",
+ "warranty",
+ "attachment",
+ "receipt",
+ "thumbnail"
+ ],
+ "x-enum-varnames": [
+ "DefaultType",
+ "TypePhoto",
+ "TypeManual",
+ "TypeWarranty",
+ "TypeAttachment",
+ "TypeReceipt",
+ "TypeThumbnail"
+ ]
+ },
+ "authroles.Role": {
+ "type": "string",
+ "enum": [
+ "user",
+ "admin",
+ "user",
+ "attachments"
+ ],
+ "x-enum-varnames": [
+ "DefaultRole",
+ "RoleAdmin",
+ "RoleUser",
+ "RoleAttachments"
+ ]
+ },
"currencies.Currency": {
"type": "object",
"properties": {
@@ -2113,6 +2174,891 @@ const docTemplate = `{
}
}
},
+ "ent.Attachment": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AttachmentQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AttachmentEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "path": {
+ "description": "Path holds the value of the \"path\" field.",
+ "type": "string"
+ },
+ "primary": {
+ "description": "Primary holds the value of the \"primary\" field.",
+ "type": "boolean"
+ },
+ "title": {
+ "description": "Title holds the value of the \"title\" field.",
+ "type": "string"
+ },
+ "type": {
+ "description": "Type holds the value of the \"type\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/attachment.Type"
+ }
+ ]
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.AttachmentEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ },
+ "thumbnail": {
+ "description": "Thumbnail holds the value of the thumbnail edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Attachment"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthRoles": {
+ "type": "object",
+ "properties": {
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AuthRolesQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthRolesEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "integer"
+ },
+ "role": {
+ "description": "Role holds the value of the \"role\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/authroles.Role"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthRolesEdges": {
+ "type": "object",
+ "properties": {
+ "token": {
+ "description": "Token holds the value of the token edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthTokens"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthTokens": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AuthTokensQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthTokensEdges"
+ }
+ ]
+ },
+ "expires_at": {
+ "description": "ExpiresAt holds the value of the \"expires_at\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token holds the value of the \"token\" field.",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.AuthTokensEdges": {
+ "type": "object",
+ "properties": {
+ "roles": {
+ "description": "Roles holds the value of the roles edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthRoles"
+ }
+ ]
+ },
+ "user": {
+ "description": "User holds the value of the user edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.User"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Group": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "currency": {
+ "description": "Currency holds the value of the \"currency\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the GroupQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.GroupEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.GroupEdges": {
+ "type": "object",
+ "properties": {
+ "invitation_tokens": {
+ "description": "InvitationTokens holds the value of the invitation_tokens edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.GroupInvitationToken"
+ }
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "labels": {
+ "description": "Labels holds the value of the labels edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Label"
+ }
+ },
+ "locations": {
+ "description": "Locations holds the value of the locations edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Location"
+ }
+ },
+ "notifiers": {
+ "description": "Notifiers holds the value of the notifiers edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Notifier"
+ }
+ },
+ "users": {
+ "description": "Users holds the value of the users edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.User"
+ }
+ }
+ }
+ },
+ "ent.GroupInvitationToken": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the GroupInvitationTokenQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.GroupInvitationTokenEdges"
+ }
+ ]
+ },
+ "expires_at": {
+ "description": "ExpiresAt holds the value of the \"expires_at\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token holds the value of the \"token\" field.",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "uses": {
+ "description": "Uses holds the value of the \"uses\" field.",
+ "type": "integer"
+ }
+ }
+ },
+ "ent.GroupInvitationTokenEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Item": {
+ "type": "object",
+ "properties": {
+ "archived": {
+ "description": "Archived holds the value of the \"archived\" field.",
+ "type": "boolean"
+ },
+ "asset_id": {
+ "description": "AssetID holds the value of the \"asset_id\" field.",
+ "type": "integer"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the ItemQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.ItemEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "import_ref": {
+ "description": "ImportRef holds the value of the \"import_ref\" field.",
+ "type": "string"
+ },
+ "insured": {
+ "description": "Insured holds the value of the \"insured\" field.",
+ "type": "boolean"
+ },
+ "lifetime_warranty": {
+ "description": "LifetimeWarranty holds the value of the \"lifetime_warranty\" field.",
+ "type": "boolean"
+ },
+ "manufacturer": {
+ "description": "Manufacturer holds the value of the \"manufacturer\" field.",
+ "type": "string"
+ },
+ "model_number": {
+ "description": "ModelNumber holds the value of the \"model_number\" field.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "notes": {
+ "description": "Notes holds the value of the \"notes\" field.",
+ "type": "string"
+ },
+ "purchase_from": {
+ "description": "PurchaseFrom holds the value of the \"purchase_from\" field.",
+ "type": "string"
+ },
+ "purchase_price": {
+ "description": "PurchasePrice holds the value of the \"purchase_price\" field.",
+ "type": "number"
+ },
+ "purchase_time": {
+ "description": "PurchaseTime holds the value of the \"purchase_time\" field.",
+ "type": "string"
+ },
+ "quantity": {
+ "description": "Quantity holds the value of the \"quantity\" field.",
+ "type": "integer"
+ },
+ "serial_number": {
+ "description": "SerialNumber holds the value of the \"serial_number\" field.",
+ "type": "string"
+ },
+ "sold_notes": {
+ "description": "SoldNotes holds the value of the \"sold_notes\" field.",
+ "type": "string"
+ },
+ "sold_price": {
+ "description": "SoldPrice holds the value of the \"sold_price\" field.",
+ "type": "number"
+ },
+ "sold_time": {
+ "description": "SoldTime holds the value of the \"sold_time\" field.",
+ "type": "string"
+ },
+ "sold_to": {
+ "description": "SoldTo holds the value of the \"sold_to\" field.",
+ "type": "string"
+ },
+ "sync_child_items_locations": {
+ "description": "SyncChildItemsLocations holds the value of the \"sync_child_items_locations\" field.",
+ "type": "boolean"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "warranty_details": {
+ "description": "WarrantyDetails holds the value of the \"warranty_details\" field.",
+ "type": "string"
+ },
+ "warranty_expires": {
+ "description": "WarrantyExpires holds the value of the \"warranty_expires\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.ItemEdges": {
+ "type": "object",
+ "properties": {
+ "attachments": {
+ "description": "Attachments holds the value of the attachments edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Attachment"
+ }
+ },
+ "children": {
+ "description": "Children holds the value of the children edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "fields": {
+ "description": "Fields holds the value of the fields edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.ItemField"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "label": {
+ "description": "Label holds the value of the label edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Label"
+ }
+ },
+ "location": {
+ "description": "Location holds the value of the location edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Location"
+ }
+ ]
+ },
+ "maintenance_entries": {
+ "description": "MaintenanceEntries holds the value of the maintenance_entries edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.MaintenanceEntry"
+ }
+ },
+ "parent": {
+ "description": "Parent holds the value of the parent edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.ItemField": {
+ "type": "object",
+ "properties": {
+ "boolean_value": {
+ "description": "BooleanValue holds the value of the \"boolean_value\" field.",
+ "type": "boolean"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the ItemFieldQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.ItemFieldEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "number_value": {
+ "description": "NumberValue holds the value of the \"number_value\" field.",
+ "type": "integer"
+ },
+ "text_value": {
+ "description": "TextValue holds the value of the \"text_value\" field.",
+ "type": "string"
+ },
+ "time_value": {
+ "description": "TimeValue holds the value of the \"time_value\" field.",
+ "type": "string"
+ },
+ "type": {
+ "description": "Type holds the value of the \"type\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/itemfield.Type"
+ }
+ ]
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.ItemFieldEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Label": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "description": "Color holds the value of the \"color\" field.",
+ "type": "string"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the LabelQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.LabelEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.LabelEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ }
+ }
+ },
+ "ent.Location": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the LocationQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.LocationEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.LocationEdges": {
+ "type": "object",
+ "properties": {
+ "children": {
+ "description": "Children holds the value of the children edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Location"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "parent": {
+ "description": "Parent holds the value of the parent edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Location"
+ }
+ ]
+ }
+ }
+ },
+ "ent.MaintenanceEntry": {
+ "type": "object",
+ "properties": {
+ "cost": {
+ "description": "Cost holds the value of the \"cost\" field.",
+ "type": "number"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "date": {
+ "description": "Date holds the value of the \"date\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the MaintenanceEntryQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.MaintenanceEntryEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "item_id": {
+ "description": "ItemID holds the value of the \"item_id\" field.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "scheduled_date": {
+ "description": "ScheduledDate holds the value of the \"scheduled_date\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.MaintenanceEntryEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Notifier": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the NotifierQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.NotifierEdges"
+ }
+ ]
+ },
+ "group_id": {
+ "description": "GroupID holds the value of the \"group_id\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "is_active": {
+ "description": "IsActive holds the value of the \"is_active\" field.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "user_id": {
+ "description": "UserID holds the value of the \"user_id\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.NotifierEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "user": {
+ "description": "User holds the value of the user edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.User"
+ }
+ ]
+ }
+ }
+ },
+ "ent.User": {
+ "type": "object",
+ "properties": {
+ "activated_on": {
+ "description": "ActivatedOn holds the value of the \"activated_on\" field.",
+ "type": "string"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.UserEdges"
+ }
+ ]
+ },
+ "email": {
+ "description": "Email holds the value of the \"email\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "is_superuser": {
+ "description": "IsSuperuser holds the value of the \"is_superuser\" field.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "role": {
+ "description": "Role holds the value of the \"role\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/user.Role"
+ }
+ ]
+ },
+ "superuser": {
+ "description": "Superuser holds the value of the \"superuser\" field.",
+ "type": "boolean"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.UserEdges": {
+ "type": "object",
+ "properties": {
+ "auth_tokens": {
+ "description": "AuthTokens holds the value of the auth_tokens edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.AuthTokens"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "notifiers": {
+ "description": "Notifiers holds the value of the notifiers edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Notifier"
+ }
+ }
+ }
+ },
+ "itemfield.Type": {
+ "type": "string",
+ "enum": [
+ "text",
+ "number",
+ "boolean",
+ "time"
+ ],
+ "x-enum-varnames": [
+ "TypeText",
+ "TypeNumber",
+ "TypeBoolean",
+ "TypeTime"
+ ]
+ },
"repo.Group": {
"type": "object",
"properties": {
@@ -2182,6 +3128,9 @@ const docTemplate = `{
"primary": {
"type": "boolean"
},
+ "thumbnail": {
+ "$ref": "#/definitions/ent.Attachment"
+ },
"title": {
"type": "string"
},
@@ -2296,7 +3245,9 @@ const docTemplate = `{
"type": "string"
},
"imageId": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
},
"insured": {
"type": "boolean"
@@ -2375,6 +3326,11 @@ const docTemplate = `{
"syncChildItemsLocations": {
"type": "boolean"
},
+ "thumbnailId": {
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
+ },
"updatedAt": {
"type": "string"
},
@@ -2433,7 +3389,9 @@ const docTemplate = `{
"type": "string"
},
"imageId": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
},
"insured": {
"type": "boolean"
@@ -2467,6 +3425,11 @@ const docTemplate = `{
"description": "Sale details",
"type": "string"
},
+ "thumbnailId": {
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
+ },
"updatedAt": {
"type": "string"
}
@@ -3097,6 +4060,19 @@ const docTemplate = `{
}
}
},
+ "user.Role": {
+ "type": "string",
+ "enum": [
+ "user",
+ "user",
+ "owner"
+ ],
+ "x-enum-varnames": [
+ "DefaultRole",
+ "RoleUser",
+ "RoleOwner"
+ ]
+ },
"v1.APISummary": {
"type": "object",
"properties": {
diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json
index 8e41e7b2..78260159 100644
--- a/backend/app/api/static/docs/swagger.json
+++ b/backend/app/api/static/docs/swagger.json
@@ -16,6 +16,31 @@
"host": "demo.homebox.software",
"basePath": "/api",
"paths": {
+ "/v1/actions/create-missing-thumbnails": {
+ "post": {
+ "security": [
+ {
+ "Bearer": []
+ }
+ ],
+ "description": "Creates thumbnails for items that are missing them",
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Actions"
+ ],
+ "summary": "Create Missing Thumbnails",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/v1.ActionAmountResult"
+ }
+ }
+ }
+ }
+ },
"/v1/actions/ensure-asset-ids": {
"post": {
"security": [
@@ -2094,6 +2119,42 @@
}
},
"definitions": {
+ "attachment.Type": {
+ "type": "string",
+ "enum": [
+ "attachment",
+ "photo",
+ "manual",
+ "warranty",
+ "attachment",
+ "receipt",
+ "thumbnail"
+ ],
+ "x-enum-varnames": [
+ "DefaultType",
+ "TypePhoto",
+ "TypeManual",
+ "TypeWarranty",
+ "TypeAttachment",
+ "TypeReceipt",
+ "TypeThumbnail"
+ ]
+ },
+ "authroles.Role": {
+ "type": "string",
+ "enum": [
+ "user",
+ "admin",
+ "user",
+ "attachments"
+ ],
+ "x-enum-varnames": [
+ "DefaultRole",
+ "RoleAdmin",
+ "RoleUser",
+ "RoleAttachments"
+ ]
+ },
"currencies.Currency": {
"type": "object",
"properties": {
@@ -2111,6 +2172,891 @@
}
}
},
+ "ent.Attachment": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AttachmentQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AttachmentEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "path": {
+ "description": "Path holds the value of the \"path\" field.",
+ "type": "string"
+ },
+ "primary": {
+ "description": "Primary holds the value of the \"primary\" field.",
+ "type": "boolean"
+ },
+ "title": {
+ "description": "Title holds the value of the \"title\" field.",
+ "type": "string"
+ },
+ "type": {
+ "description": "Type holds the value of the \"type\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/attachment.Type"
+ }
+ ]
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.AttachmentEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ },
+ "thumbnail": {
+ "description": "Thumbnail holds the value of the thumbnail edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Attachment"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthRoles": {
+ "type": "object",
+ "properties": {
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AuthRolesQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthRolesEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "integer"
+ },
+ "role": {
+ "description": "Role holds the value of the \"role\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/authroles.Role"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthRolesEdges": {
+ "type": "object",
+ "properties": {
+ "token": {
+ "description": "Token holds the value of the token edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthTokens"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthTokens": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AuthTokensQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthTokensEdges"
+ }
+ ]
+ },
+ "expires_at": {
+ "description": "ExpiresAt holds the value of the \"expires_at\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token holds the value of the \"token\" field.",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.AuthTokensEdges": {
+ "type": "object",
+ "properties": {
+ "roles": {
+ "description": "Roles holds the value of the roles edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthRoles"
+ }
+ ]
+ },
+ "user": {
+ "description": "User holds the value of the user edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.User"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Group": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "currency": {
+ "description": "Currency holds the value of the \"currency\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the GroupQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.GroupEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.GroupEdges": {
+ "type": "object",
+ "properties": {
+ "invitation_tokens": {
+ "description": "InvitationTokens holds the value of the invitation_tokens edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.GroupInvitationToken"
+ }
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "labels": {
+ "description": "Labels holds the value of the labels edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Label"
+ }
+ },
+ "locations": {
+ "description": "Locations holds the value of the locations edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Location"
+ }
+ },
+ "notifiers": {
+ "description": "Notifiers holds the value of the notifiers edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Notifier"
+ }
+ },
+ "users": {
+ "description": "Users holds the value of the users edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.User"
+ }
+ }
+ }
+ },
+ "ent.GroupInvitationToken": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the GroupInvitationTokenQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.GroupInvitationTokenEdges"
+ }
+ ]
+ },
+ "expires_at": {
+ "description": "ExpiresAt holds the value of the \"expires_at\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token holds the value of the \"token\" field.",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "uses": {
+ "description": "Uses holds the value of the \"uses\" field.",
+ "type": "integer"
+ }
+ }
+ },
+ "ent.GroupInvitationTokenEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Item": {
+ "type": "object",
+ "properties": {
+ "archived": {
+ "description": "Archived holds the value of the \"archived\" field.",
+ "type": "boolean"
+ },
+ "asset_id": {
+ "description": "AssetID holds the value of the \"asset_id\" field.",
+ "type": "integer"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the ItemQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.ItemEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "import_ref": {
+ "description": "ImportRef holds the value of the \"import_ref\" field.",
+ "type": "string"
+ },
+ "insured": {
+ "description": "Insured holds the value of the \"insured\" field.",
+ "type": "boolean"
+ },
+ "lifetime_warranty": {
+ "description": "LifetimeWarranty holds the value of the \"lifetime_warranty\" field.",
+ "type": "boolean"
+ },
+ "manufacturer": {
+ "description": "Manufacturer holds the value of the \"manufacturer\" field.",
+ "type": "string"
+ },
+ "model_number": {
+ "description": "ModelNumber holds the value of the \"model_number\" field.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "notes": {
+ "description": "Notes holds the value of the \"notes\" field.",
+ "type": "string"
+ },
+ "purchase_from": {
+ "description": "PurchaseFrom holds the value of the \"purchase_from\" field.",
+ "type": "string"
+ },
+ "purchase_price": {
+ "description": "PurchasePrice holds the value of the \"purchase_price\" field.",
+ "type": "number"
+ },
+ "purchase_time": {
+ "description": "PurchaseTime holds the value of the \"purchase_time\" field.",
+ "type": "string"
+ },
+ "quantity": {
+ "description": "Quantity holds the value of the \"quantity\" field.",
+ "type": "integer"
+ },
+ "serial_number": {
+ "description": "SerialNumber holds the value of the \"serial_number\" field.",
+ "type": "string"
+ },
+ "sold_notes": {
+ "description": "SoldNotes holds the value of the \"sold_notes\" field.",
+ "type": "string"
+ },
+ "sold_price": {
+ "description": "SoldPrice holds the value of the \"sold_price\" field.",
+ "type": "number"
+ },
+ "sold_time": {
+ "description": "SoldTime holds the value of the \"sold_time\" field.",
+ "type": "string"
+ },
+ "sold_to": {
+ "description": "SoldTo holds the value of the \"sold_to\" field.",
+ "type": "string"
+ },
+ "sync_child_items_locations": {
+ "description": "SyncChildItemsLocations holds the value of the \"sync_child_items_locations\" field.",
+ "type": "boolean"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "warranty_details": {
+ "description": "WarrantyDetails holds the value of the \"warranty_details\" field.",
+ "type": "string"
+ },
+ "warranty_expires": {
+ "description": "WarrantyExpires holds the value of the \"warranty_expires\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.ItemEdges": {
+ "type": "object",
+ "properties": {
+ "attachments": {
+ "description": "Attachments holds the value of the attachments edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Attachment"
+ }
+ },
+ "children": {
+ "description": "Children holds the value of the children edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "fields": {
+ "description": "Fields holds the value of the fields edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.ItemField"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "label": {
+ "description": "Label holds the value of the label edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Label"
+ }
+ },
+ "location": {
+ "description": "Location holds the value of the location edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Location"
+ }
+ ]
+ },
+ "maintenance_entries": {
+ "description": "MaintenanceEntries holds the value of the maintenance_entries edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.MaintenanceEntry"
+ }
+ },
+ "parent": {
+ "description": "Parent holds the value of the parent edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.ItemField": {
+ "type": "object",
+ "properties": {
+ "boolean_value": {
+ "description": "BooleanValue holds the value of the \"boolean_value\" field.",
+ "type": "boolean"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the ItemFieldQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.ItemFieldEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "number_value": {
+ "description": "NumberValue holds the value of the \"number_value\" field.",
+ "type": "integer"
+ },
+ "text_value": {
+ "description": "TextValue holds the value of the \"text_value\" field.",
+ "type": "string"
+ },
+ "time_value": {
+ "description": "TimeValue holds the value of the \"time_value\" field.",
+ "type": "string"
+ },
+ "type": {
+ "description": "Type holds the value of the \"type\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/itemfield.Type"
+ }
+ ]
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.ItemFieldEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Label": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "description": "Color holds the value of the \"color\" field.",
+ "type": "string"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the LabelQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.LabelEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.LabelEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ }
+ }
+ },
+ "ent.Location": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the LocationQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.LocationEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.LocationEdges": {
+ "type": "object",
+ "properties": {
+ "children": {
+ "description": "Children holds the value of the children edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Location"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "parent": {
+ "description": "Parent holds the value of the parent edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Location"
+ }
+ ]
+ }
+ }
+ },
+ "ent.MaintenanceEntry": {
+ "type": "object",
+ "properties": {
+ "cost": {
+ "description": "Cost holds the value of the \"cost\" field.",
+ "type": "number"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "date": {
+ "description": "Date holds the value of the \"date\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the MaintenanceEntryQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.MaintenanceEntryEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "item_id": {
+ "description": "ItemID holds the value of the \"item_id\" field.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "scheduled_date": {
+ "description": "ScheduledDate holds the value of the \"scheduled_date\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.MaintenanceEntryEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Notifier": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the NotifierQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.NotifierEdges"
+ }
+ ]
+ },
+ "group_id": {
+ "description": "GroupID holds the value of the \"group_id\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "is_active": {
+ "description": "IsActive holds the value of the \"is_active\" field.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "user_id": {
+ "description": "UserID holds the value of the \"user_id\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.NotifierEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "user": {
+ "description": "User holds the value of the user edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.User"
+ }
+ ]
+ }
+ }
+ },
+ "ent.User": {
+ "type": "object",
+ "properties": {
+ "activated_on": {
+ "description": "ActivatedOn holds the value of the \"activated_on\" field.",
+ "type": "string"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.UserEdges"
+ }
+ ]
+ },
+ "email": {
+ "description": "Email holds the value of the \"email\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "is_superuser": {
+ "description": "IsSuperuser holds the value of the \"is_superuser\" field.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "role": {
+ "description": "Role holds the value of the \"role\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/user.Role"
+ }
+ ]
+ },
+ "superuser": {
+ "description": "Superuser holds the value of the \"superuser\" field.",
+ "type": "boolean"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.UserEdges": {
+ "type": "object",
+ "properties": {
+ "auth_tokens": {
+ "description": "AuthTokens holds the value of the auth_tokens edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.AuthTokens"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "notifiers": {
+ "description": "Notifiers holds the value of the notifiers edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Notifier"
+ }
+ }
+ }
+ },
+ "itemfield.Type": {
+ "type": "string",
+ "enum": [
+ "text",
+ "number",
+ "boolean",
+ "time"
+ ],
+ "x-enum-varnames": [
+ "TypeText",
+ "TypeNumber",
+ "TypeBoolean",
+ "TypeTime"
+ ]
+ },
"repo.Group": {
"type": "object",
"properties": {
@@ -2180,6 +3126,9 @@
"primary": {
"type": "boolean"
},
+ "thumbnail": {
+ "$ref": "#/definitions/ent.Attachment"
+ },
"title": {
"type": "string"
},
@@ -2294,7 +3243,9 @@
"type": "string"
},
"imageId": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
},
"insured": {
"type": "boolean"
@@ -2373,6 +3324,11 @@
"syncChildItemsLocations": {
"type": "boolean"
},
+ "thumbnailId": {
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
+ },
"updatedAt": {
"type": "string"
},
@@ -2431,7 +3387,9 @@
"type": "string"
},
"imageId": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
},
"insured": {
"type": "boolean"
@@ -2465,6 +3423,11 @@
"description": "Sale details",
"type": "string"
},
+ "thumbnailId": {
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
+ },
"updatedAt": {
"type": "string"
}
@@ -3095,6 +4058,19 @@
}
}
},
+ "user.Role": {
+ "type": "string",
+ "enum": [
+ "user",
+ "user",
+ "owner"
+ ],
+ "x-enum-varnames": [
+ "DefaultRole",
+ "RoleUser",
+ "RoleOwner"
+ ]
+ },
"v1.APISummary": {
"type": "object",
"properties": {
diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml
index f807c315..db182f31 100644
--- a/backend/app/api/static/docs/swagger.yaml
+++ b/backend/app/api/static/docs/swagger.yaml
@@ -1,5 +1,35 @@
basePath: /api
definitions:
+ attachment.Type:
+ enum:
+ - attachment
+ - photo
+ - manual
+ - warranty
+ - attachment
+ - receipt
+ - thumbnail
+ type: string
+ x-enum-varnames:
+ - DefaultType
+ - TypePhoto
+ - TypeManual
+ - TypeWarranty
+ - TypeAttachment
+ - TypeReceipt
+ - TypeThumbnail
+ authroles.Role:
+ enum:
+ - user
+ - admin
+ - user
+ - attachments
+ type: string
+ x-enum-varnames:
+ - DefaultRole
+ - RoleAdmin
+ - RoleUser
+ - RoleAttachments
currencies.Currency:
properties:
code:
@@ -11,6 +41,608 @@ definitions:
symbol:
type: string
type: object
+ ent.Attachment:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.AttachmentEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the AttachmentQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ path:
+ description: Path holds the value of the "path" field.
+ type: string
+ primary:
+ description: Primary holds the value of the "primary" field.
+ type: boolean
+ title:
+ description: Title holds the value of the "title" field.
+ type: string
+ type:
+ allOf:
+ - $ref: '#/definitions/attachment.Type'
+ description: Type holds the value of the "type" field.
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.AttachmentEdges:
+ properties:
+ item:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Item holds the value of the item edge.
+ thumbnail:
+ allOf:
+ - $ref: '#/definitions/ent.Attachment'
+ description: Thumbnail holds the value of the thumbnail edge.
+ type: object
+ ent.AuthRoles:
+ properties:
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.AuthRolesEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the AuthRolesQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: integer
+ role:
+ allOf:
+ - $ref: '#/definitions/authroles.Role'
+ description: Role holds the value of the "role" field.
+ type: object
+ ent.AuthRolesEdges:
+ properties:
+ token:
+ allOf:
+ - $ref: '#/definitions/ent.AuthTokens'
+ description: Token holds the value of the token edge.
+ type: object
+ ent.AuthTokens:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.AuthTokensEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the AuthTokensQuery when eager-loading is set.
+ expires_at:
+ description: ExpiresAt holds the value of the "expires_at" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ token:
+ description: Token holds the value of the "token" field.
+ items:
+ type: integer
+ type: array
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.AuthTokensEdges:
+ properties:
+ roles:
+ allOf:
+ - $ref: '#/definitions/ent.AuthRoles'
+ description: Roles holds the value of the roles edge.
+ user:
+ allOf:
+ - $ref: '#/definitions/ent.User'
+ description: User holds the value of the user edge.
+ type: object
+ ent.Group:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ currency:
+ description: Currency holds the value of the "currency" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.GroupEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the GroupQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.GroupEdges:
+ properties:
+ invitation_tokens:
+ description: InvitationTokens holds the value of the invitation_tokens edge.
+ items:
+ $ref: '#/definitions/ent.GroupInvitationToken'
+ type: array
+ items:
+ description: Items holds the value of the items edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ labels:
+ description: Labels holds the value of the labels edge.
+ items:
+ $ref: '#/definitions/ent.Label'
+ type: array
+ locations:
+ description: Locations holds the value of the locations edge.
+ items:
+ $ref: '#/definitions/ent.Location'
+ type: array
+ notifiers:
+ description: Notifiers holds the value of the notifiers edge.
+ items:
+ $ref: '#/definitions/ent.Notifier'
+ type: array
+ users:
+ description: Users holds the value of the users edge.
+ items:
+ $ref: '#/definitions/ent.User'
+ type: array
+ type: object
+ ent.GroupInvitationToken:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.GroupInvitationTokenEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the GroupInvitationTokenQuery when eager-loading is set.
+ expires_at:
+ description: ExpiresAt holds the value of the "expires_at" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ token:
+ description: Token holds the value of the "token" field.
+ items:
+ type: integer
+ type: array
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ uses:
+ description: Uses holds the value of the "uses" field.
+ type: integer
+ type: object
+ ent.GroupInvitationTokenEdges:
+ properties:
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ type: object
+ ent.Item:
+ properties:
+ archived:
+ description: Archived holds the value of the "archived" field.
+ type: boolean
+ asset_id:
+ description: AssetID holds the value of the "asset_id" field.
+ type: integer
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.ItemEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the ItemQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ import_ref:
+ description: ImportRef holds the value of the "import_ref" field.
+ type: string
+ insured:
+ description: Insured holds the value of the "insured" field.
+ type: boolean
+ lifetime_warranty:
+ description: LifetimeWarranty holds the value of the "lifetime_warranty" field.
+ type: boolean
+ manufacturer:
+ description: Manufacturer holds the value of the "manufacturer" field.
+ type: string
+ model_number:
+ description: ModelNumber holds the value of the "model_number" field.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ notes:
+ description: Notes holds the value of the "notes" field.
+ type: string
+ purchase_from:
+ description: PurchaseFrom holds the value of the "purchase_from" field.
+ type: string
+ purchase_price:
+ description: PurchasePrice holds the value of the "purchase_price" field.
+ type: number
+ purchase_time:
+ description: PurchaseTime holds the value of the "purchase_time" field.
+ type: string
+ quantity:
+ description: Quantity holds the value of the "quantity" field.
+ type: integer
+ serial_number:
+ description: SerialNumber holds the value of the "serial_number" field.
+ type: string
+ sold_notes:
+ description: SoldNotes holds the value of the "sold_notes" field.
+ type: string
+ sold_price:
+ description: SoldPrice holds the value of the "sold_price" field.
+ type: number
+ sold_time:
+ description: SoldTime holds the value of the "sold_time" field.
+ type: string
+ sold_to:
+ description: SoldTo holds the value of the "sold_to" field.
+ type: string
+ sync_child_items_locations:
+ description: SyncChildItemsLocations holds the value of the "sync_child_items_locations"
+ field.
+ type: boolean
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ warranty_details:
+ description: WarrantyDetails holds the value of the "warranty_details" field.
+ type: string
+ warranty_expires:
+ description: WarrantyExpires holds the value of the "warranty_expires" field.
+ type: string
+ type: object
+ ent.ItemEdges:
+ properties:
+ attachments:
+ description: Attachments holds the value of the attachments edge.
+ items:
+ $ref: '#/definitions/ent.Attachment'
+ type: array
+ children:
+ description: Children holds the value of the children edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ fields:
+ description: Fields holds the value of the fields edge.
+ items:
+ $ref: '#/definitions/ent.ItemField'
+ type: array
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ label:
+ description: Label holds the value of the label edge.
+ items:
+ $ref: '#/definitions/ent.Label'
+ type: array
+ location:
+ allOf:
+ - $ref: '#/definitions/ent.Location'
+ description: Location holds the value of the location edge.
+ maintenance_entries:
+ description: MaintenanceEntries holds the value of the maintenance_entries
+ edge.
+ items:
+ $ref: '#/definitions/ent.MaintenanceEntry'
+ type: array
+ parent:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Parent holds the value of the parent edge.
+ type: object
+ ent.ItemField:
+ properties:
+ boolean_value:
+ description: BooleanValue holds the value of the "boolean_value" field.
+ type: boolean
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.ItemFieldEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the ItemFieldQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ number_value:
+ description: NumberValue holds the value of the "number_value" field.
+ type: integer
+ text_value:
+ description: TextValue holds the value of the "text_value" field.
+ type: string
+ time_value:
+ description: TimeValue holds the value of the "time_value" field.
+ type: string
+ type:
+ allOf:
+ - $ref: '#/definitions/itemfield.Type'
+ description: Type holds the value of the "type" field.
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.ItemFieldEdges:
+ properties:
+ item:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Item holds the value of the item edge.
+ type: object
+ ent.Label:
+ properties:
+ color:
+ description: Color holds the value of the "color" field.
+ type: string
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.LabelEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the LabelQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.LabelEdges:
+ properties:
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ items:
+ description: Items holds the value of the items edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ type: object
+ ent.Location:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.LocationEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the LocationQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.LocationEdges:
+ properties:
+ children:
+ description: Children holds the value of the children edge.
+ items:
+ $ref: '#/definitions/ent.Location'
+ type: array
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ items:
+ description: Items holds the value of the items edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ parent:
+ allOf:
+ - $ref: '#/definitions/ent.Location'
+ description: Parent holds the value of the parent edge.
+ type: object
+ ent.MaintenanceEntry:
+ properties:
+ cost:
+ description: Cost holds the value of the "cost" field.
+ type: number
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ date:
+ description: Date holds the value of the "date" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.MaintenanceEntryEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the MaintenanceEntryQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ item_id:
+ description: ItemID holds the value of the "item_id" field.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ scheduled_date:
+ description: ScheduledDate holds the value of the "scheduled_date" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.MaintenanceEntryEdges:
+ properties:
+ item:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Item holds the value of the item edge.
+ type: object
+ ent.Notifier:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.NotifierEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the NotifierQuery when eager-loading is set.
+ group_id:
+ description: GroupID holds the value of the "group_id" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ is_active:
+ description: IsActive holds the value of the "is_active" field.
+ type: boolean
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ user_id:
+ description: UserID holds the value of the "user_id" field.
+ type: string
+ type: object
+ ent.NotifierEdges:
+ properties:
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ user:
+ allOf:
+ - $ref: '#/definitions/ent.User'
+ description: User holds the value of the user edge.
+ type: object
+ ent.User:
+ properties:
+ activated_on:
+ description: ActivatedOn holds the value of the "activated_on" field.
+ type: string
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.UserEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the UserQuery when eager-loading is set.
+ email:
+ description: Email holds the value of the "email" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ is_superuser:
+ description: IsSuperuser holds the value of the "is_superuser" field.
+ type: boolean
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ role:
+ allOf:
+ - $ref: '#/definitions/user.Role'
+ description: Role holds the value of the "role" field.
+ superuser:
+ description: Superuser holds the value of the "superuser" field.
+ type: boolean
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.UserEdges:
+ properties:
+ auth_tokens:
+ description: AuthTokens holds the value of the auth_tokens edge.
+ items:
+ $ref: '#/definitions/ent.AuthTokens'
+ type: array
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ notifiers:
+ description: Notifiers holds the value of the notifiers edge.
+ items:
+ $ref: '#/definitions/ent.Notifier'
+ type: array
+ type: object
+ itemfield.Type:
+ enum:
+ - text
+ - number
+ - boolean
+ - time
+ type: string
+ x-enum-varnames:
+ - TypeText
+ - TypeNumber
+ - TypeBoolean
+ - TypeTime
repo.Group:
properties:
createdAt:
@@ -56,6 +688,8 @@ definitions:
type: string
primary:
type: boolean
+ thumbnail:
+ $ref: '#/definitions/ent.Attachment'
title:
type: string
type:
@@ -134,6 +768,8 @@ definitions:
type: string
imageId:
type: string
+ x-nullable: true
+ x-omitempty: true
insured:
type: boolean
labels:
@@ -185,6 +821,10 @@ definitions:
type: string
syncChildItemsLocations:
type: boolean
+ thumbnailId:
+ type: string
+ x-nullable: true
+ x-omitempty: true
updatedAt:
type: string
warrantyDetails:
@@ -225,6 +865,8 @@ definitions:
type: string
imageId:
type: string
+ x-nullable: true
+ x-omitempty: true
insured:
type: boolean
labels:
@@ -246,6 +888,10 @@ definitions:
soldTime:
description: Sale details
type: string
+ thumbnailId:
+ type: string
+ x-nullable: true
+ x-omitempty: true
updatedAt:
type: string
type: object
@@ -670,6 +1316,16 @@ definitions:
token:
type: string
type: object
+ user.Role:
+ enum:
+ - user
+ - user
+ - owner
+ type: string
+ x-enum-varnames:
+ - DefaultRole
+ - RoleUser
+ - RoleOwner
v1.APISummary:
properties:
allowRegistration:
@@ -779,6 +1435,21 @@ info:
title: Homebox API
version: "1.0"
paths:
+ /v1/actions/create-missing-thumbnails:
+ post:
+ description: Creates thumbnails for items that are missing them
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/v1.ActionAmountResult'
+ security:
+ - Bearer: []
+ summary: Create Missing Thumbnails
+ tags:
+ - Actions
/v1/actions/ensure-asset-ids:
post:
description: Ensures all items in the database have an asset ID
diff --git a/backend/go.mod b/backend/go.mod
index 6625e151..725a6b7f 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -8,6 +8,8 @@ require (
entgo.io/ent v0.14.4
github.com/ardanlabs/conf/v3 v3.8.0
github.com/containrrr/shoutrrr v0.8.0
+ github.com/gen2brain/avif v0.4.4
+ github.com/gen2brain/webp v0.5.5
github.com/go-chi/chi/v5 v5.2.1
github.com/go-playground/validator/v10 v10.26.0
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
@@ -30,6 +32,9 @@ require (
github.com/yeqown/go-qrcode/writer/standard v1.3.0
github.com/zeebo/blake3 v0.2.4
gocloud.dev v0.41.0
+ gocloud.dev/pubsub/kafkapubsub v0.41.0
+ gocloud.dev/pubsub/natspubsub v0.41.0
+ gocloud.dev/pubsub/rabbitpubsub v0.41.0
golang.org/x/crypto v0.39.0
golang.org/x/image v0.28.0
modernc.org/sqlite v1.37.1
@@ -44,17 +49,22 @@ require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.4.2 // indirect
cloud.google.com/go/monitoring v1.24.1 // indirect
+ cloud.google.com/go/pubsub v1.48.0 // indirect
cloud.google.com/go/storage v1.51.0 // indirect
+ github.com/Azure/azure-amqp-common-go/v3 v3.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 // indirect
+ github.com/Azure/go-amqp v1.4.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
+ github.com/IBM/sarama v1.45.1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
@@ -75,6 +85,8 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sns v1.34.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sqs v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
@@ -84,6 +96,9 @@ require (
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
+ 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/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
@@ -103,15 +118,25 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+ github.com/golang/snappy v1.0.0 // indirect
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/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
+ github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl/v2 v2.13.0 // indirect
+ github.com/jcmturner/aescts/v2 v2.0.0 // indirect
+ github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
+ github.com/jcmturner/gofork v1.7.6 // indirect
+ github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
+ 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/cpuid/v2 v2.0.12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@@ -121,14 +146,21 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
+ github.com/nats-io/nats.go v1.40.1 // indirect
+ github.com/nats-io/nkeys v0.4.10 // indirect
+ github.com/nats-io/nuid v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // 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.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/rabbitmq/amqp091-go v1.10.0 // indirect
+ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
+ github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
diff --git a/backend/go.sum b/backend/go.sum
index 9a3ea08f..182f18db 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -19,12 +19,16 @@ cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR
cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw=
cloud.google.com/go/monitoring v1.24.1 h1:vKiypZVFD/5a3BbQMvI4gZdl8445ITzXFh257XBgrS0=
cloud.google.com/go/monitoring v1.24.1/go.mod h1:Z05d1/vn9NaujqY2voG6pVQXoJGbp+r3laV+LySt9K0=
+cloud.google.com/go/pubsub v1.48.0 h1:ntFpQVrr10Wj/GXSOpxGmexGynldv/bFp25H0jy8aOs=
+cloud.google.com/go/pubsub v1.48.0/go.mod h1:AAtyjyIT/+zaY1ERKFJbefOvkUxRDNp3nD6TdfdqUZk=
cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw=
cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc=
cloud.google.com/go/trace v1.11.5 h1:CALS1loyxJMnRiCwZSpdf8ac7iCsjreMxFD2WGxzzHU=
cloud.google.com/go/trace v1.11.5/go.mod h1:TwblCcqNInriu5/qzaeYEIH7wzUcchSdeY2l5wL3Eec=
entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=
entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
@@ -33,14 +37,25 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
+github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.8.0 h1:JNgM3Tz592fUHU2vgwgvOgKxo5s9Ki0y2wicBeckn70=
+github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.8.0/go.mod h1:6vUKmzY17h6dpn9ZLAhM4R/rcrltBeq52qZIkUR7Oro=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
+github.com/Azure/go-amqp v0.17.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
+github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4=
+github.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=
github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
@@ -56,6 +71,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
+github.com/IBM/sarama v1.45.1 h1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=
+github.com/IBM/sarama v1.45.1/go.mod h1:qifDhA3VWSrQ1TjSMyxDl3nYL3oX2C83u+G6L79sq4w=
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.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
@@ -98,6 +115,10 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91Liq
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
+github.com/aws/aws-sdk-go-v2/service/sns v1.34.2 h1:PajtbJ/5bEo6iUAIGMYnK8ljqg2F1h4mMCGh1acjN30=
+github.com/aws/aws-sdk-go-v2/service/sns v1.34.2/go.mod h1:PJtxxMdj747j8DeZENRTTYAz/lx/pADn/U0k7YNNiUY=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.38.3 h1:j5BchjfDoS7K26vPdyJlyxBIIBGDflq3qjjJKBDlbcI=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.38.3/go.mod h1:Bar4MrRxeqdn6XIh8JGfiXuFRmyrrsZNTJotxEJmWW0=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 h1:pdgODsAhGo4dvzC3JAG5Ce0PX8kWXrTZGx+jxADD+5E=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 h1:90uX0veLKcdHVfvxhkWUQSCi5VabtwMLFutYiRke4oo=
@@ -115,6 +136,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
+github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@@ -122,10 +145,17 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
+github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
+github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
+github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
+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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -146,8 +176,15 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
+github.com/gen2brain/avif v0.4.4 h1:Ga/ss7qcWWQm2bxFpnjYjhJsNfZrWs5RsyklgFjKRSE=
+github.com/gen2brain/avif v0.4.4/go.mod h1:/XCaJcjZraQwKVhpu9aEd9aLOssYOawLvhMBtmHVGqk=
+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.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -193,6 +230,8 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -204,11 +243,14 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
+github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -235,22 +277,48 @@ github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrk
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc=
github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0=
github.com/hay-kot/httpkit v0.0.11 h1:ZdB2uqsFBSDpfUoClGK5c5orjBjQkEVSXh7fZX5FKEk=
github.com/hay-kot/httpkit v0.0.11/go.mod h1:0kZdk5/swzdfqfg2c6pBWimcgeJ9PTyO97EbHnYl2Sw=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
+github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
+github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
+github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
+github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
+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/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -282,8 +350,20 @@ github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEu
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
+github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
+github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/nats-io/jwt/v2 v2.5.0 h1:WQQ40AAlqqfx+f6ku+i0pOVm+ASirD4fUh+oQsiE9Ak=
+github.com/nats-io/jwt/v2 v2.5.0/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
+github.com/nats-io/nats-server/v2 v2.9.23 h1:6Wj6H6QpP9FMlpCyWUaNu2yeZ/qGj+mdRkZ1wbikExU=
+github.com/nats-io/nats-server/v2 v2.9.23/go.mod h1:wEjrEy9vnqIGE4Pqz4/c75v9Pmaq7My2IgFmnykc4C0=
+github.com/nats-io/nats.go v1.40.1 h1:MLjDkdsbGUeCMKFyCFoLnNn/HDTqcgVa3EQm+pMNDPk=
+github.com/nats-io/nats.go v1.40.1/go.mod h1:wV73x0FSI/orHPSYoyMeJB+KajMDoWyXmFaRrrYaaTo=
+github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc=
+github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U=
+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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@@ -293,6 +373,8 @@ 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=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
+github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
+github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -306,6 +388,10 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+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-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -327,6 +413,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -339,6 +426,8 @@ github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSy
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
+github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
+github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -384,13 +473,23 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+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=
gocloud.dev v0.41.0 h1:qBKd9jZkBKEghYbP/uThpomhedK5s2Gy6Lz7h/zYYrM=
gocloud.dev v0.41.0/go.mod h1:IetpBcWLUwroOOxKr90lhsZ8vWxeSkuszBnW62sbcf0=
+gocloud.dev/pubsub/kafkapubsub v0.41.0 h1:Ft6YB77ejqk++VjW51UP39RH/WDAMtv6ed3+PHMxBzg=
+gocloud.dev/pubsub/kafkapubsub v0.41.0/go.mod h1:kJf4c6b+4yJk6nXmv33yXKblbrgWmrYCzI5QEsr27G0=
+gocloud.dev/pubsub/natspubsub v0.41.0 h1:UxNb0DiAzdnyHut6jcCG7u6lsB/hzxTyZ/RHWeCUJ4Q=
+gocloud.dev/pubsub/natspubsub v0.41.0/go.mod h1:uCBKjwvIcuNuf3+ft4wUI9hPHHKQvroxq9ZPB/410ac=
+gocloud.dev/pubsub/rabbitpubsub v0.41.0 h1:RutvHbacZxlFr0t3wlr+kz63j53UOfHY3PJR8NKN1EI=
+gocloud.dev/pubsub/rabbitpubsub v0.41.0/go.mod h1:s7oQXOlQ2FOj8XmYMv5Ocgs1t+8hIXfsKaWGgECM9SQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
@@ -415,10 +514,12 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
diff --git a/backend/internal/core/services/main_test.go b/backend/internal/core/services/main_test.go
index 462f0d61..b792ab37 100644
--- a/backend/internal/core/services/main_test.go
+++ b/backend/internal/core/services/main_test.go
@@ -65,6 +65,10 @@ func MainNoExit(m *testing.M) int {
tRepos = repo.New(tClient, tbus, config.Storage{
PrefixPath: "/",
ConnString: "file://" + os.TempDir(),
+ }, "mem://{{ .Topic }}", config.Thumbnail{
+ Enabled: false,
+ Width: 0,
+ Height: 0,
})
err = os.MkdirAll(os.TempDir()+"/homebox", 0o755)
diff --git a/backend/internal/data/ent/attachment.go b/backend/internal/data/ent/attachment.go
index d4550939..ab47ee35 100644
--- a/backend/internal/data/ent/attachment.go
+++ b/backend/internal/data/ent/attachment.go
@@ -33,18 +33,21 @@ type Attachment struct {
Path string `json:"path,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AttachmentQuery when eager-loading is set.
- Edges AttachmentEdges `json:"edges"`
- item_attachments *uuid.UUID
- selectValues sql.SelectValues
+ Edges AttachmentEdges `json:"edges"`
+ attachment_thumbnail *uuid.UUID
+ item_attachments *uuid.UUID
+ selectValues sql.SelectValues
}
// AttachmentEdges holds the relations/edges for other nodes in the graph.
type AttachmentEdges struct {
// Item holds the value of the item edge.
Item *Item `json:"item,omitempty"`
+ // Thumbnail holds the value of the thumbnail edge.
+ Thumbnail *Attachment `json:"thumbnail,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
- loadedTypes [1]bool
+ loadedTypes [2]bool
}
// ItemOrErr returns the Item value or an error if the edge
@@ -58,6 +61,17 @@ func (e AttachmentEdges) ItemOrErr() (*Item, error) {
return nil, &NotLoadedError{edge: "item"}
}
+// ThumbnailOrErr returns the Thumbnail value or an error if the edge
+// was not loaded in eager-loading, or loaded but was not found.
+func (e AttachmentEdges) ThumbnailOrErr() (*Attachment, error) {
+ if e.Thumbnail != nil {
+ return e.Thumbnail, nil
+ } else if e.loadedTypes[1] {
+ return nil, &NotFoundError{label: attachment.Label}
+ }
+ return nil, &NotLoadedError{edge: "thumbnail"}
+}
+
// scanValues returns the types for scanning values from sql.Rows.
func (*Attachment) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
@@ -71,7 +85,9 @@ func (*Attachment) scanValues(columns []string) ([]any, error) {
values[i] = new(sql.NullTime)
case attachment.FieldID:
values[i] = new(uuid.UUID)
- case attachment.ForeignKeys[0]: // item_attachments
+ case attachment.ForeignKeys[0]: // attachment_thumbnail
+ values[i] = &sql.NullScanner{S: new(uuid.UUID)}
+ case attachment.ForeignKeys[1]: // item_attachments
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
values[i] = new(sql.UnknownType)
@@ -131,6 +147,13 @@ func (a *Attachment) assignValues(columns []string, values []any) error {
a.Path = value.String
}
case attachment.ForeignKeys[0]:
+ if value, ok := values[i].(*sql.NullScanner); !ok {
+ return fmt.Errorf("unexpected type %T for field attachment_thumbnail", values[i])
+ } else if value.Valid {
+ a.attachment_thumbnail = new(uuid.UUID)
+ *a.attachment_thumbnail = *value.S.(*uuid.UUID)
+ }
+ case attachment.ForeignKeys[1]:
if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field item_attachments", values[i])
} else if value.Valid {
@@ -155,6 +178,11 @@ func (a *Attachment) QueryItem() *ItemQuery {
return NewAttachmentClient(a.config).QueryItem(a)
}
+// QueryThumbnail queries the "thumbnail" edge of the Attachment entity.
+func (a *Attachment) QueryThumbnail() *AttachmentQuery {
+ return NewAttachmentClient(a.config).QueryThumbnail(a)
+}
+
// Update returns a builder for updating this Attachment.
// Note that you need to call Attachment.Unwrap() before calling this method if this Attachment
// was returned from a transaction, and the transaction was committed or rolled back.
diff --git a/backend/internal/data/ent/attachment/attachment.go b/backend/internal/data/ent/attachment/attachment.go
index 56429027..419f6eab 100644
--- a/backend/internal/data/ent/attachment/attachment.go
+++ b/backend/internal/data/ent/attachment/attachment.go
@@ -30,6 +30,8 @@ const (
FieldPath = "path"
// EdgeItem holds the string denoting the item edge name in mutations.
EdgeItem = "item"
+ // EdgeThumbnail holds the string denoting the thumbnail edge name in mutations.
+ EdgeThumbnail = "thumbnail"
// Table holds the table name of the attachment in the database.
Table = "attachments"
// ItemTable is the table that holds the item relation/edge.
@@ -39,6 +41,10 @@ const (
ItemInverseTable = "items"
// ItemColumn is the table column denoting the item relation/edge.
ItemColumn = "item_attachments"
+ // ThumbnailTable is the table that holds the thumbnail relation/edge.
+ ThumbnailTable = "attachments"
+ // ThumbnailColumn is the table column denoting the thumbnail relation/edge.
+ ThumbnailColumn = "attachment_thumbnail"
)
// Columns holds all SQL columns for attachment fields.
@@ -55,6 +61,7 @@ var Columns = []string{
// ForeignKeys holds the SQL foreign-keys that are owned by the "attachments"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
+ "attachment_thumbnail",
"item_attachments",
}
@@ -103,6 +110,7 @@ const (
TypeWarranty Type = "warranty"
TypeAttachment Type = "attachment"
TypeReceipt Type = "receipt"
+ TypeThumbnail Type = "thumbnail"
)
func (_type Type) String() string {
@@ -112,7 +120,7 @@ func (_type Type) String() string {
// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save.
func TypeValidator(_type Type) error {
switch _type {
- case TypePhoto, TypeManual, TypeWarranty, TypeAttachment, TypeReceipt:
+ case TypePhoto, TypeManual, TypeWarranty, TypeAttachment, TypeReceipt, TypeThumbnail:
return nil
default:
return fmt.Errorf("attachment: invalid enum value for type field: %q", _type)
@@ -163,6 +171,13 @@ func ByItemField(field string, opts ...sql.OrderTermOption) OrderOption {
sqlgraph.OrderByNeighborTerms(s, newItemStep(), sql.OrderByField(field, opts...))
}
}
+
+// ByThumbnailField orders the results by thumbnail field.
+func ByThumbnailField(field string, opts ...sql.OrderTermOption) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborTerms(s, newThumbnailStep(), sql.OrderByField(field, opts...))
+ }
+}
func newItemStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
@@ -170,3 +185,10 @@ func newItemStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.M2O, true, ItemTable, ItemColumn),
)
}
+func newThumbnailStep() *sqlgraph.Step {
+ return sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.To(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.O2O, false, ThumbnailTable, ThumbnailColumn),
+ )
+}
diff --git a/backend/internal/data/ent/attachment/where.go b/backend/internal/data/ent/attachment/where.go
index fd7ffb3d..75e17790 100644
--- a/backend/internal/data/ent/attachment/where.go
+++ b/backend/internal/data/ent/attachment/where.go
@@ -344,6 +344,29 @@ func HasItemWith(preds ...predicate.Item) predicate.Attachment {
})
}
+// HasThumbnail applies the HasEdge predicate on the "thumbnail" edge.
+func HasThumbnail() predicate.Attachment {
+ return predicate.Attachment(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.O2O, false, ThumbnailTable, ThumbnailColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasThumbnailWith applies the HasEdge predicate on the "thumbnail" edge with a given conditions (other predicates).
+func HasThumbnailWith(preds ...predicate.Attachment) predicate.Attachment {
+ return predicate.Attachment(func(s *sql.Selector) {
+ step := newThumbnailStep()
+ sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
+ for _, p := range preds {
+ p(s)
+ }
+ })
+ })
+}
+
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Attachment) predicate.Attachment {
return predicate.Attachment(sql.AndPredicates(predicates...))
diff --git a/backend/internal/data/ent/attachment_create.go b/backend/internal/data/ent/attachment_create.go
index fde2773b..d5c9d69c 100644
--- a/backend/internal/data/ent/attachment_create.go
+++ b/backend/internal/data/ent/attachment_create.go
@@ -126,11 +126,38 @@ func (ac *AttachmentCreate) SetItemID(id uuid.UUID) *AttachmentCreate {
return ac
}
+// SetNillableItemID sets the "item" edge to the Item entity by ID if the given value is not nil.
+func (ac *AttachmentCreate) SetNillableItemID(id *uuid.UUID) *AttachmentCreate {
+ if id != nil {
+ ac = ac.SetItemID(*id)
+ }
+ return ac
+}
+
// SetItem sets the "item" edge to the Item entity.
func (ac *AttachmentCreate) SetItem(i *Item) *AttachmentCreate {
return ac.SetItemID(i.ID)
}
+// SetThumbnailID sets the "thumbnail" edge to the Attachment entity by ID.
+func (ac *AttachmentCreate) SetThumbnailID(id uuid.UUID) *AttachmentCreate {
+ ac.mutation.SetThumbnailID(id)
+ return ac
+}
+
+// SetNillableThumbnailID sets the "thumbnail" edge to the Attachment entity by ID if the given value is not nil.
+func (ac *AttachmentCreate) SetNillableThumbnailID(id *uuid.UUID) *AttachmentCreate {
+ if id != nil {
+ ac = ac.SetThumbnailID(*id)
+ }
+ return ac
+}
+
+// SetThumbnail sets the "thumbnail" edge to the Attachment entity.
+func (ac *AttachmentCreate) SetThumbnail(a *Attachment) *AttachmentCreate {
+ return ac.SetThumbnailID(a.ID)
+}
+
// Mutation returns the AttachmentMutation object of the builder.
func (ac *AttachmentCreate) Mutation() *AttachmentMutation {
return ac.mutation
@@ -221,9 +248,6 @@ func (ac *AttachmentCreate) check() error {
if _, ok := ac.mutation.Path(); !ok {
return &ValidationError{Name: "path", err: errors.New(`ent: missing required field "Attachment.path"`)}
}
- if len(ac.mutation.ItemIDs()) == 0 {
- return &ValidationError{Name: "item", err: errors.New(`ent: missing required edge "Attachment.item"`)}
- }
return nil
}
@@ -300,6 +324,23 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
_node.item_attachments = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
+ if nodes := ac.mutation.ThumbnailIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2O,
+ Inverse: false,
+ Table: attachment.ThumbnailTable,
+ Columns: []string{attachment.ThumbnailColumn},
+ Bidi: true,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _node.attachment_thumbnail = &nodes[0]
+ _spec.Edges = append(_spec.Edges, edge)
+ }
return _node, _spec
}
diff --git a/backend/internal/data/ent/attachment_query.go b/backend/internal/data/ent/attachment_query.go
index a05f9590..5f264dd1 100644
--- a/backend/internal/data/ent/attachment_query.go
+++ b/backend/internal/data/ent/attachment_query.go
@@ -20,12 +20,13 @@ import (
// AttachmentQuery is the builder for querying Attachment entities.
type AttachmentQuery struct {
config
- ctx *QueryContext
- order []attachment.OrderOption
- inters []Interceptor
- predicates []predicate.Attachment
- withItem *ItemQuery
- withFKs bool
+ ctx *QueryContext
+ order []attachment.OrderOption
+ inters []Interceptor
+ predicates []predicate.Attachment
+ withItem *ItemQuery
+ withThumbnail *AttachmentQuery
+ withFKs bool
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -84,6 +85,28 @@ func (aq *AttachmentQuery) QueryItem() *ItemQuery {
return query
}
+// QueryThumbnail chains the current query on the "thumbnail" edge.
+func (aq *AttachmentQuery) QueryThumbnail() *AttachmentQuery {
+ query := (&AttachmentClient{config: aq.config}).Query()
+ query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
+ if err := aq.prepareQuery(ctx); err != nil {
+ return nil, err
+ }
+ selector := aq.sqlQuery(ctx)
+ if err := selector.Err(); err != nil {
+ return nil, err
+ }
+ step := sqlgraph.NewStep(
+ sqlgraph.From(attachment.Table, attachment.FieldID, selector),
+ sqlgraph.To(attachment.Table, attachment.FieldID),
+ sqlgraph.Edge(sqlgraph.O2O, false, attachment.ThumbnailTable, attachment.ThumbnailColumn),
+ )
+ fromU = sqlgraph.SetNeighbors(aq.driver.Dialect(), step)
+ return fromU, nil
+ }
+ return query
+}
+
// First returns the first Attachment entity from the query.
// Returns a *NotFoundError when no Attachment was found.
func (aq *AttachmentQuery) First(ctx context.Context) (*Attachment, error) {
@@ -271,12 +294,13 @@ func (aq *AttachmentQuery) Clone() *AttachmentQuery {
return nil
}
return &AttachmentQuery{
- config: aq.config,
- ctx: aq.ctx.Clone(),
- order: append([]attachment.OrderOption{}, aq.order...),
- inters: append([]Interceptor{}, aq.inters...),
- predicates: append([]predicate.Attachment{}, aq.predicates...),
- withItem: aq.withItem.Clone(),
+ config: aq.config,
+ ctx: aq.ctx.Clone(),
+ order: append([]attachment.OrderOption{}, aq.order...),
+ inters: append([]Interceptor{}, aq.inters...),
+ predicates: append([]predicate.Attachment{}, aq.predicates...),
+ withItem: aq.withItem.Clone(),
+ withThumbnail: aq.withThumbnail.Clone(),
// clone intermediate query.
sql: aq.sql.Clone(),
path: aq.path,
@@ -294,6 +318,17 @@ func (aq *AttachmentQuery) WithItem(opts ...func(*ItemQuery)) *AttachmentQuery {
return aq
}
+// WithThumbnail tells the query-builder to eager-load the nodes that are connected to
+// the "thumbnail" edge. The optional arguments are used to configure the query builder of the edge.
+func (aq *AttachmentQuery) WithThumbnail(opts ...func(*AttachmentQuery)) *AttachmentQuery {
+ query := (&AttachmentClient{config: aq.config}).Query()
+ for _, opt := range opts {
+ opt(query)
+ }
+ aq.withThumbnail = query
+ return aq
+}
+
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
@@ -373,11 +408,12 @@ func (aq *AttachmentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*A
nodes = []*Attachment{}
withFKs = aq.withFKs
_spec = aq.querySpec()
- loadedTypes = [1]bool{
+ loadedTypes = [2]bool{
aq.withItem != nil,
+ aq.withThumbnail != nil,
}
)
- if aq.withItem != nil {
+ if aq.withItem != nil || aq.withThumbnail != nil {
withFKs = true
}
if withFKs {
@@ -407,6 +443,12 @@ func (aq *AttachmentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*A
return nil, err
}
}
+ if query := aq.withThumbnail; query != nil {
+ if err := aq.loadThumbnail(ctx, query, nodes, nil,
+ func(n *Attachment, e *Attachment) { n.Edges.Thumbnail = e }); err != nil {
+ return nil, err
+ }
+ }
return nodes, nil
}
@@ -442,6 +484,38 @@ func (aq *AttachmentQuery) loadItem(ctx context.Context, query *ItemQuery, nodes
}
return nil
}
+func (aq *AttachmentQuery) loadThumbnail(ctx context.Context, query *AttachmentQuery, nodes []*Attachment, init func(*Attachment), assign func(*Attachment, *Attachment)) error {
+ ids := make([]uuid.UUID, 0, len(nodes))
+ nodeids := make(map[uuid.UUID][]*Attachment)
+ for i := range nodes {
+ if nodes[i].attachment_thumbnail == nil {
+ continue
+ }
+ fk := *nodes[i].attachment_thumbnail
+ if _, ok := nodeids[fk]; !ok {
+ ids = append(ids, fk)
+ }
+ nodeids[fk] = append(nodeids[fk], nodes[i])
+ }
+ if len(ids) == 0 {
+ return nil
+ }
+ query.Where(attachment.IDIn(ids...))
+ neighbors, err := query.All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, n := range neighbors {
+ nodes, ok := nodeids[n.ID]
+ if !ok {
+ return fmt.Errorf(`unexpected foreign-key "attachment_thumbnail" returned %v`, n.ID)
+ }
+ for i := range nodes {
+ assign(nodes[i], n)
+ }
+ }
+ return nil
+}
func (aq *AttachmentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := aq.querySpec()
diff --git a/backend/internal/data/ent/attachment_update.go b/backend/internal/data/ent/attachment_update.go
index c6dcb93b..32b73894 100644
--- a/backend/internal/data/ent/attachment_update.go
+++ b/backend/internal/data/ent/attachment_update.go
@@ -98,11 +98,38 @@ func (au *AttachmentUpdate) SetItemID(id uuid.UUID) *AttachmentUpdate {
return au
}
+// SetNillableItemID sets the "item" edge to the Item entity by ID if the given value is not nil.
+func (au *AttachmentUpdate) SetNillableItemID(id *uuid.UUID) *AttachmentUpdate {
+ if id != nil {
+ au = au.SetItemID(*id)
+ }
+ return au
+}
+
// SetItem sets the "item" edge to the Item entity.
func (au *AttachmentUpdate) SetItem(i *Item) *AttachmentUpdate {
return au.SetItemID(i.ID)
}
+// SetThumbnailID sets the "thumbnail" edge to the Attachment entity by ID.
+func (au *AttachmentUpdate) SetThumbnailID(id uuid.UUID) *AttachmentUpdate {
+ au.mutation.SetThumbnailID(id)
+ return au
+}
+
+// SetNillableThumbnailID sets the "thumbnail" edge to the Attachment entity by ID if the given value is not nil.
+func (au *AttachmentUpdate) SetNillableThumbnailID(id *uuid.UUID) *AttachmentUpdate {
+ if id != nil {
+ au = au.SetThumbnailID(*id)
+ }
+ return au
+}
+
+// SetThumbnail sets the "thumbnail" edge to the Attachment entity.
+func (au *AttachmentUpdate) SetThumbnail(a *Attachment) *AttachmentUpdate {
+ return au.SetThumbnailID(a.ID)
+}
+
// Mutation returns the AttachmentMutation object of the builder.
func (au *AttachmentUpdate) Mutation() *AttachmentMutation {
return au.mutation
@@ -114,6 +141,12 @@ func (au *AttachmentUpdate) ClearItem() *AttachmentUpdate {
return au
}
+// ClearThumbnail clears the "thumbnail" edge to the Attachment entity.
+func (au *AttachmentUpdate) ClearThumbnail() *AttachmentUpdate {
+ au.mutation.ClearThumbnail()
+ return au
+}
+
// Save executes the query and returns the number of nodes affected by the update operation.
func (au *AttachmentUpdate) Save(ctx context.Context) (int, error) {
au.defaults()
@@ -157,9 +190,6 @@ func (au *AttachmentUpdate) check() error {
return &ValidationError{Name: "type", err: fmt.Errorf(`ent: validator failed for field "Attachment.type": %w`, err)}
}
}
- if au.mutation.ItemCleared() && len(au.mutation.ItemIDs()) > 0 {
- return errors.New(`ent: clearing a required unique edge "Attachment.item"`)
- }
return nil
}
@@ -219,6 +249,35 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
+ if au.mutation.ThumbnailCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2O,
+ Inverse: false,
+ Table: attachment.ThumbnailTable,
+ Columns: []string{attachment.ThumbnailColumn},
+ Bidi: true,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := au.mutation.ThumbnailIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2O,
+ Inverse: false,
+ Table: attachment.ThumbnailTable,
+ Columns: []string{attachment.ThumbnailColumn},
+ Bidi: true,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges.Add = append(_spec.Edges.Add, edge)
+ }
if n, err = sqlgraph.UpdateNodes(ctx, au.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{attachment.Label}
@@ -307,11 +366,38 @@ func (auo *AttachmentUpdateOne) SetItemID(id uuid.UUID) *AttachmentUpdateOne {
return auo
}
+// SetNillableItemID sets the "item" edge to the Item entity by ID if the given value is not nil.
+func (auo *AttachmentUpdateOne) SetNillableItemID(id *uuid.UUID) *AttachmentUpdateOne {
+ if id != nil {
+ auo = auo.SetItemID(*id)
+ }
+ return auo
+}
+
// SetItem sets the "item" edge to the Item entity.
func (auo *AttachmentUpdateOne) SetItem(i *Item) *AttachmentUpdateOne {
return auo.SetItemID(i.ID)
}
+// SetThumbnailID sets the "thumbnail" edge to the Attachment entity by ID.
+func (auo *AttachmentUpdateOne) SetThumbnailID(id uuid.UUID) *AttachmentUpdateOne {
+ auo.mutation.SetThumbnailID(id)
+ return auo
+}
+
+// SetNillableThumbnailID sets the "thumbnail" edge to the Attachment entity by ID if the given value is not nil.
+func (auo *AttachmentUpdateOne) SetNillableThumbnailID(id *uuid.UUID) *AttachmentUpdateOne {
+ if id != nil {
+ auo = auo.SetThumbnailID(*id)
+ }
+ return auo
+}
+
+// SetThumbnail sets the "thumbnail" edge to the Attachment entity.
+func (auo *AttachmentUpdateOne) SetThumbnail(a *Attachment) *AttachmentUpdateOne {
+ return auo.SetThumbnailID(a.ID)
+}
+
// Mutation returns the AttachmentMutation object of the builder.
func (auo *AttachmentUpdateOne) Mutation() *AttachmentMutation {
return auo.mutation
@@ -323,6 +409,12 @@ func (auo *AttachmentUpdateOne) ClearItem() *AttachmentUpdateOne {
return auo
}
+// ClearThumbnail clears the "thumbnail" edge to the Attachment entity.
+func (auo *AttachmentUpdateOne) ClearThumbnail() *AttachmentUpdateOne {
+ auo.mutation.ClearThumbnail()
+ return auo
+}
+
// Where appends a list predicates to the AttachmentUpdate builder.
func (auo *AttachmentUpdateOne) Where(ps ...predicate.Attachment) *AttachmentUpdateOne {
auo.mutation.Where(ps...)
@@ -379,9 +471,6 @@ func (auo *AttachmentUpdateOne) check() error {
return &ValidationError{Name: "type", err: fmt.Errorf(`ent: validator failed for field "Attachment.type": %w`, err)}
}
}
- if auo.mutation.ItemCleared() && len(auo.mutation.ItemIDs()) > 0 {
- return errors.New(`ent: clearing a required unique edge "Attachment.item"`)
- }
return nil
}
@@ -458,6 +547,35 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
+ if auo.mutation.ThumbnailCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2O,
+ Inverse: false,
+ Table: attachment.ThumbnailTable,
+ Columns: []string{attachment.ThumbnailColumn},
+ Bidi: true,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := auo.mutation.ThumbnailIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2O,
+ Inverse: false,
+ Table: attachment.ThumbnailTable,
+ Columns: []string{attachment.ThumbnailColumn},
+ Bidi: true,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges.Add = append(_spec.Edges.Add, edge)
+ }
_node = &Attachment{config: auo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
diff --git a/backend/internal/data/ent/client.go b/backend/internal/data/ent/client.go
index 7320f8a0..babad945 100644
--- a/backend/internal/data/ent/client.go
+++ b/backend/internal/data/ent/client.go
@@ -422,6 +422,22 @@ func (c *AttachmentClient) QueryItem(a *Attachment) *ItemQuery {
return query
}
+// QueryThumbnail queries the thumbnail edge of a Attachment.
+func (c *AttachmentClient) QueryThumbnail(a *Attachment) *AttachmentQuery {
+ query := (&AttachmentClient{config: c.config}).Query()
+ query.path = func(context.Context) (fromV *sql.Selector, _ error) {
+ id := a.ID
+ step := sqlgraph.NewStep(
+ sqlgraph.From(attachment.Table, attachment.FieldID, id),
+ sqlgraph.To(attachment.Table, attachment.FieldID),
+ sqlgraph.Edge(sqlgraph.O2O, false, attachment.ThumbnailTable, attachment.ThumbnailColumn),
+ )
+ fromV = sqlgraph.Neighbors(a.driver.Dialect(), step)
+ return fromV, nil
+ }
+ return query
+}
+
// Hooks returns the client hooks.
func (c *AttachmentClient) Hooks() []Hook {
return c.hooks.Attachment
diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go
index 73030146..15a35a2d 100644
--- a/backend/internal/data/ent/migrate/schema.go
+++ b/backend/internal/data/ent/migrate/schema.go
@@ -13,11 +13,12 @@ var (
{Name: "id", Type: field.TypeUUID},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
- {Name: "type", Type: field.TypeEnum, Enums: []string{"photo", "manual", "warranty", "attachment", "receipt"}, Default: "attachment"},
+ {Name: "type", Type: field.TypeEnum, Enums: []string{"photo", "manual", "warranty", "attachment", "receipt", "thumbnail"}, Default: "attachment"},
{Name: "primary", Type: field.TypeBool, Default: false},
{Name: "title", Type: field.TypeString, Default: ""},
{Name: "path", Type: field.TypeString, Default: ""},
- {Name: "item_attachments", Type: field.TypeUUID},
+ {Name: "attachment_thumbnail", Type: field.TypeUUID, Unique: true, Nullable: true},
+ {Name: "item_attachments", Type: field.TypeUUID, Nullable: true},
}
// AttachmentsTable holds the schema information for the "attachments" table.
AttachmentsTable = &schema.Table{
@@ -26,8 +27,14 @@ var (
PrimaryKey: []*schema.Column{AttachmentsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
- Symbol: "attachments_items_attachments",
+ Symbol: "attachments_attachments_thumbnail",
Columns: []*schema.Column{AttachmentsColumns[7]},
+ RefColumns: []*schema.Column{AttachmentsColumns[0]},
+ OnDelete: schema.SetNull,
+ },
+ {
+ Symbol: "attachments_items_attachments",
+ Columns: []*schema.Column{AttachmentsColumns[8]},
RefColumns: []*schema.Column{ItemsColumns[0]},
OnDelete: schema.Cascade,
},
@@ -443,7 +450,8 @@ var (
)
func init() {
- AttachmentsTable.ForeignKeys[0].RefTable = ItemsTable
+ AttachmentsTable.ForeignKeys[0].RefTable = AttachmentsTable
+ AttachmentsTable.ForeignKeys[1].RefTable = ItemsTable
AuthRolesTable.ForeignKeys[0].RefTable = AuthTokensTable
AuthTokensTable.ForeignKeys[0].RefTable = UsersTable
GroupInvitationTokensTable.ForeignKeys[0].RefTable = GroupsTable
diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go
index 74f187d8..a4985f26 100644
--- a/backend/internal/data/ent/mutation.go
+++ b/backend/internal/data/ent/mutation.go
@@ -53,21 +53,23 @@ const (
// AttachmentMutation represents an operation that mutates the Attachment nodes in the graph.
type AttachmentMutation struct {
config
- op Op
- typ string
- id *uuid.UUID
- created_at *time.Time
- updated_at *time.Time
- _type *attachment.Type
- primary *bool
- title *string
- _path *string
- clearedFields map[string]struct{}
- item *uuid.UUID
- cleareditem bool
- done bool
- oldValue func(context.Context) (*Attachment, error)
- predicates []predicate.Attachment
+ op Op
+ typ string
+ id *uuid.UUID
+ created_at *time.Time
+ updated_at *time.Time
+ _type *attachment.Type
+ primary *bool
+ title *string
+ _path *string
+ clearedFields map[string]struct{}
+ item *uuid.UUID
+ cleareditem bool
+ thumbnail *uuid.UUID
+ clearedthumbnail bool
+ done bool
+ oldValue func(context.Context) (*Attachment, error)
+ predicates []predicate.Attachment
}
var _ ent.Mutation = (*AttachmentMutation)(nil)
@@ -429,6 +431,45 @@ func (m *AttachmentMutation) ResetItem() {
m.cleareditem = false
}
+// SetThumbnailID sets the "thumbnail" edge to the Attachment entity by id.
+func (m *AttachmentMutation) SetThumbnailID(id uuid.UUID) {
+ m.thumbnail = &id
+}
+
+// ClearThumbnail clears the "thumbnail" edge to the Attachment entity.
+func (m *AttachmentMutation) ClearThumbnail() {
+ m.clearedthumbnail = true
+}
+
+// ThumbnailCleared reports if the "thumbnail" edge to the Attachment entity was cleared.
+func (m *AttachmentMutation) ThumbnailCleared() bool {
+ return m.clearedthumbnail
+}
+
+// ThumbnailID returns the "thumbnail" edge ID in the mutation.
+func (m *AttachmentMutation) ThumbnailID() (id uuid.UUID, exists bool) {
+ if m.thumbnail != nil {
+ return *m.thumbnail, true
+ }
+ return
+}
+
+// ThumbnailIDs returns the "thumbnail" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// ThumbnailID instead. It exists only for internal usage by the builders.
+func (m *AttachmentMutation) ThumbnailIDs() (ids []uuid.UUID) {
+ if id := m.thumbnail; id != nil {
+ ids = append(ids, *id)
+ }
+ return
+}
+
+// ResetThumbnail resets all changes to the "thumbnail" edge.
+func (m *AttachmentMutation) ResetThumbnail() {
+ m.thumbnail = nil
+ m.clearedthumbnail = false
+}
+
// Where appends a list predicates to the AttachmentMutation builder.
func (m *AttachmentMutation) Where(ps ...predicate.Attachment) {
m.predicates = append(m.predicates, ps...)
@@ -647,10 +688,13 @@ func (m *AttachmentMutation) ResetField(name string) error {
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *AttachmentMutation) AddedEdges() []string {
- edges := make([]string, 0, 1)
+ edges := make([]string, 0, 2)
if m.item != nil {
edges = append(edges, attachment.EdgeItem)
}
+ if m.thumbnail != nil {
+ edges = append(edges, attachment.EdgeThumbnail)
+ }
return edges
}
@@ -662,13 +706,17 @@ func (m *AttachmentMutation) AddedIDs(name string) []ent.Value {
if id := m.item; id != nil {
return []ent.Value{*id}
}
+ case attachment.EdgeThumbnail:
+ if id := m.thumbnail; id != nil {
+ return []ent.Value{*id}
+ }
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *AttachmentMutation) RemovedEdges() []string {
- edges := make([]string, 0, 1)
+ edges := make([]string, 0, 2)
return edges
}
@@ -680,10 +728,13 @@ func (m *AttachmentMutation) RemovedIDs(name string) []ent.Value {
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *AttachmentMutation) ClearedEdges() []string {
- edges := make([]string, 0, 1)
+ edges := make([]string, 0, 2)
if m.cleareditem {
edges = append(edges, attachment.EdgeItem)
}
+ if m.clearedthumbnail {
+ edges = append(edges, attachment.EdgeThumbnail)
+ }
return edges
}
@@ -693,6 +744,8 @@ func (m *AttachmentMutation) EdgeCleared(name string) bool {
switch name {
case attachment.EdgeItem:
return m.cleareditem
+ case attachment.EdgeThumbnail:
+ return m.clearedthumbnail
}
return false
}
@@ -704,6 +757,9 @@ func (m *AttachmentMutation) ClearEdge(name string) error {
case attachment.EdgeItem:
m.ClearItem()
return nil
+ case attachment.EdgeThumbnail:
+ m.ClearThumbnail()
+ return nil
}
return fmt.Errorf("unknown Attachment unique edge %s", name)
}
@@ -715,6 +771,9 @@ func (m *AttachmentMutation) ResetEdge(name string) error {
case attachment.EdgeItem:
m.ResetItem()
return nil
+ case attachment.EdgeThumbnail:
+ m.ResetThumbnail()
+ return nil
}
return fmt.Errorf("unknown Attachment edge %s", name)
}
diff --git a/backend/internal/data/ent/schema/attachment.go b/backend/internal/data/ent/schema/attachment.go
index 14577eaa..115a87a9 100644
--- a/backend/internal/data/ent/schema/attachment.go
+++ b/backend/internal/data/ent/schema/attachment.go
@@ -21,7 +21,7 @@ func (Attachment) Mixin() []ent.Mixin {
// Fields of the Attachment.
func (Attachment) Fields() []ent.Field {
return []ent.Field{
- field.Enum("type").Values("photo", "manual", "warranty", "attachment", "receipt").Default("attachment"),
+ field.Enum("type").Values("photo", "manual", "warranty", "attachment", "receipt", "thumbnail").Default("attachment"),
field.Bool("primary").Default(false),
field.String("title").Default(""),
field.String("path").Default(""),
@@ -33,7 +33,8 @@ func (Attachment) Edges() []ent.Edge {
return []ent.Edge{
edge.From("item", Item.Type).
Ref("attachments").
- Required().
+ Unique(),
+ edge.To("thumbnail", Attachment.Type).
Unique(),
}
}
diff --git a/backend/internal/data/migrations/postgres/20250619215101_add_thumbnails.sql b/backend/internal/data/migrations/postgres/20250619215101_add_thumbnails.sql
new file mode 100644
index 00000000..d19deacc
--- /dev/null
+++ b/backend/internal/data/migrations/postgres/20250619215101_add_thumbnails.sql
@@ -0,0 +1,14 @@
+-- +goose Up
+alter table public.attachments
+ alter column item_attachments drop not null;
+
+alter table public.attachments
+ add attachment_thumbnail uuid;
+
+alter table public.attachments
+ add constraint attachments_attachments_thumbnail
+ foreign key (attachment_thumbnail) references public.attachments (id);
+
+alter table public.attachments
+ add constraint attachments_no_self_reference
+ check (id != attachment_thumbnail);
\ No newline at end of file
diff --git a/backend/internal/data/migrations/sqlite3/20250619215150_add_thumbnails.sql b/backend/internal/data/migrations/sqlite3/20250619215150_add_thumbnails.sql
new file mode 100644
index 00000000..911459ff
--- /dev/null
+++ b/backend/internal/data/migrations/sqlite3/20250619215150_add_thumbnails.sql
@@ -0,0 +1,35 @@
+-- +goose Up
+create table attachments_dg_tmp
+(
+ id uuid not null
+ primary key,
+ created_at datetime not null,
+ updated_at datetime not null,
+ type text default 'attachment' not null,
+ "primary" bool default false not null,
+ path text not null,
+ title text not null,
+ item_attachments uuid
+ constraint attachments_items_attachments
+ references items
+ on delete cascade,
+ attachment_thumbnail uuid
+ constraint attachments_attachments_thumbnail
+ references attachments
+);
+
+insert into attachments_dg_tmp(id, created_at, updated_at, type, "primary", path, title, item_attachments)
+select id,
+ created_at,
+ updated_at,
+ type,
+ "primary",
+ path,
+ title,
+ item_attachments
+from attachments;
+
+drop table attachments;
+
+alter table attachments_dg_tmp
+ rename to attachments;
\ No newline at end of file
diff --git a/backend/internal/data/repo/main_test.go b/backend/internal/data/repo/main_test.go
index e8ff757b..bda60e36 100644
--- a/backend/internal/data/repo/main_test.go
+++ b/backend/internal/data/repo/main_test.go
@@ -59,6 +59,10 @@ func MainNoExit(m *testing.M) int {
tRepos = New(tClient, tbus, config.Storage{
PrefixPath: "/",
ConnString: "file://" + os.TempDir(),
+ }, "mem://{{ .Topic }}", config.Thumbnail{
+ Enabled: false,
+ Width: 0,
+ Height: 0,
})
err = os.MkdirAll(os.TempDir()+"/homebox", 0o755)
if err != nil {
diff --git a/backend/internal/data/repo/repo_item_attachments.go b/backend/internal/data/repo/repo_item_attachments.go
index 9e3aaa74..94152016 100644
--- a/backend/internal/data/repo/repo_item_attachments.go
+++ b/backend/internal/data/repo/repo_item_attachments.go
@@ -8,8 +8,15 @@ import (
"github.com/rs/zerolog/log"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
+ "github.com/sysadminsmedia/homebox/backend/pkgs/utils"
"github.com/zeebo/blake3"
+
+ "github.com/gen2brain/avif"
+ "github.com/gen2brain/webp"
+ "golang.org/x/image/draw"
+ "image"
"io"
+ "io/fs"
"net/http"
"path/filepath"
"strings"
@@ -26,24 +33,36 @@ import (
_ "gocloud.dev/blob/gcsblob"
_ "gocloud.dev/blob/memblob"
_ "gocloud.dev/blob/s3blob"
+
+ "gocloud.dev/pubsub"
+ _ "gocloud.dev/pubsub/awssnssqs"
+ _ "gocloud.dev/pubsub/azuresb"
+ _ "gocloud.dev/pubsub/gcppubsub"
+ _ "gocloud.dev/pubsub/kafkapubsub"
+ _ "gocloud.dev/pubsub/mempubsub"
+ _ "gocloud.dev/pubsub/natspubsub"
+ _ "gocloud.dev/pubsub/rabbitpubsub"
)
// AttachmentRepo is a repository for Attachments table that links Items to their
// associated files while also specifying the type of the attachment.
type AttachmentRepo struct {
- db *ent.Client
- storage config.Storage
+ db *ent.Client
+ storage config.Storage
+ pubSubConn string
+ thumbnail config.Thumbnail
}
type (
ItemAttachment struct {
- ID uuid.UUID `json:"id"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
- Type string `json:"type"`
- Primary bool `json:"primary"`
- Path string `json:"path"`
- Title string `json:"title"`
+ ID uuid.UUID `json:"id"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+ Type string `json:"type"`
+ Primary bool `json:"primary"`
+ Path string `json:"path"`
+ Title string `json:"title"`
+ Thumbnail *ent.Attachment `json:"thumbnail,omitempty"`
}
ItemAttachmentUpdate struct {
@@ -164,72 +183,15 @@ func (r *AttachmentRepo) Create(ctx context.Context, itemID uuid.UUID, doc ItemC
return nil, err
}
- // Prepare for the hashing of the file contents
- hashOut := make([]byte, 32)
-
- // Read all content into a buffer
- buf := new(bytes.Buffer)
- _, err = io.Copy(buf, doc.Content)
+ // Upload the file to the storage bucket
+ path, err := r.UploadFile(ctx, itemGroup, doc)
if err != nil {
- log.Err(err).Msg("failed to read file content")
- if rbErr := tx.Rollback(); rbErr != nil {
- return nil, rbErr
- }
- return nil, err
- }
- // Now the buffer contains all the data, use it for hashing
- contentBytes := buf.Bytes()
-
- // We use blake3 to generate a hash of the file contents, the group ID is used as context to ensure unique hashes
- // for the same file across different groups to reduce the chance of collisions
- // additionally, the hash can be used to validate the file contents if needed
- blake3.DeriveKey(itemGroup.ID.String(), contentBytes, hashOut)
-
- // Write the file to the blob storage bucket which might be a local file system or cloud storage
- bucket, err := blob.OpenBucket(ctx, r.GetConnString())
- if err != nil {
- log.Err(err).Msg("failed to open bucket")
err := tx.Rollback()
if err != nil {
return nil, err
}
return nil, err
}
- defer func(bucket *blob.Bucket) {
- err := bucket.Close()
- if err != nil {
- log.Err(err).Msg("failed to close bucket")
- err := tx.Rollback()
- if err != nil {
- log.Err(err).Msg("failed to rollback transaction after closing bucket")
- }
- }
- }(bucket)
- md5hash := md5.New()
- _, err = md5hash.Write(contentBytes)
- if err != nil {
- log.Err(err).Msg("failed to generate MD5 hash for storage")
- err = tx.Rollback()
- if err != nil {
- return nil, err
- }
- return nil, err
- }
- contentType := http.DetectContentType(contentBytes[:min(512, len(contentBytes))])
- options := &blob.WriterOptions{
- ContentType: contentType,
- ContentMD5: md5hash.Sum(nil),
- }
- path := r.path(itemGroup.ID, fmt.Sprintf("%x", hashOut))
- err = bucket.WriteAll(ctx, path, contentBytes, options)
- if err != nil {
- log.Err(err).Msg("failed to write file to bucket")
- err = tx.Rollback()
- if err != nil {
- return nil, err
- }
- return nil, err
- }
bldr = bldr.SetPath(path)
@@ -247,6 +209,34 @@ func (r *AttachmentRepo) Create(ctx context.Context, itemID uuid.UUID, doc ItemC
log.Err(err).Msg("failed to commit transaction")
return nil, err
}
+
+ if r.thumbnail.Enabled {
+ pubsubString, err := utils.GenerateSubPubConn(r.pubSubConn, "thumbnails")
+ if err != nil {
+ log.Err(err).Msg("failed to generate pubsub connection string")
+ return nil, err
+ }
+ topic, err := pubsub.OpenTopic(ctx, pubsubString)
+ if err != nil {
+ log.Err(err).Msg("failed to open pubsub topic")
+ return nil, err
+ }
+
+ err = topic.Send(ctx, &pubsub.Message{
+ Body: []byte(fmt.Sprintf("attachment_created:%s", attachmentDb.ID.String())),
+ Metadata: map[string]string{
+ "group_id": itemGroup.ID.String(),
+ "attachment_id": attachmentDb.ID.String(),
+ "title": doc.Title,
+ "path": attachmentDb.Path,
+ },
+ })
+ if err != nil {
+ log.Err(err).Msg("failed to send message to topic")
+ return nil, err
+ }
+ }
+
return attachmentDb, nil
}
@@ -255,6 +245,7 @@ func (r *AttachmentRepo) Get(ctx context.Context, id uuid.UUID) (*ent.Attachment
Query().
Where(attachment.ID(id)).
WithItem().
+ WithThumbnail().
Only(ctx)
}
@@ -332,3 +323,340 @@ func (r *AttachmentRepo) Delete(ctx context.Context, id uuid.UUID) error {
func (r *AttachmentRepo) Rename(ctx context.Context, id uuid.UUID, title string) (*ent.Attachment, error) {
return r.db.Attachment.UpdateOneID(id).SetTitle(title).Save(ctx)
}
+
+//nolint:gocyclo
+func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmentId uuid.UUID, title string, path string) error {
+ log.Debug().Msg("starting thumbnail creation")
+ tx, err := r.db.Tx(ctx)
+ if err != nil {
+ return nil
+ }
+ // If there is an error during file creation rollback the database
+ defer func() {
+ if v := recover(); v != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return
+ }
+ }
+ }()
+
+ log.Debug().Msg("set initial database transaction")
+ att := tx.Attachment.Create().
+ SetID(uuid.New()).
+ SetTitle(fmt.Sprintf("%s-thumb", title)).
+ SetType("thumbnail")
+
+ log.Debug().Msg("opening original file")
+ bucket, err := blob.OpenBucket(ctx, r.GetConnString())
+ if err != nil {
+ log.Err(err).Msg("failed to open bucket")
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ defer func(bucket *blob.Bucket) {
+ err := bucket.Close()
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return
+ }
+ log.Err(err).Msg("failed to close bucket")
+ }
+ }(bucket)
+
+ origFile, err := bucket.Open(path)
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ defer func(file fs.File) {
+ err := file.Close()
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return
+ }
+ log.Err(err).Msg("failed to close file")
+ }
+ }(origFile)
+
+ log.Debug().Msg("stat original file for file size")
+ stats, err := origFile.Stat()
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ log.Err(err).Msg("failed to stat original file")
+ return err
+ }
+
+ if stats.Size() > 100*1024*1024 {
+ return fmt.Errorf("original file %s is too large to create a thumbnail", title)
+ }
+
+ log.Debug().Msg("reading original file content")
+ contentBytes, err := io.ReadAll(origFile)
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ log.Err(err).Msg("failed to read original file content")
+ return err
+ }
+
+ log.Debug().Msg("detecting content type of original file")
+ contentType := http.DetectContentType(contentBytes[:min(512, len(contentBytes))])
+
+ switch {
+ case isImageFile(contentType):
+ log.Debug().Msg("creating thumbnail for image file")
+ img, _, err := image.Decode(bytes.NewReader(contentBytes))
+ if err != nil {
+ log.Err(err).Msg("failed to decode image file")
+ err := tx.Rollback()
+ if err != nil {
+ log.Err(err).Msg("failed to rollback transaction")
+ return err
+ }
+ return err
+ }
+ dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
+ draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
+ buf := new(bytes.Buffer)
+ err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ contentBytes := buf.Bytes()
+ log.Debug().Msg("uploading thumbnail file")
+ thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
+ Title: fmt.Sprintf("%s-thumb", title),
+ Content: bytes.NewReader(contentBytes),
+ })
+ if err != nil {
+ log.Err(err).Msg("failed to upload thumbnail file")
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ log.Debug().Msg("setting thumbnail file path in attachment")
+ att.SetPath(thumbnailFile)
+ case contentType == "image/webp":
+ log.Debug().Msg("creating thumbnail for webp file")
+ img, err := webp.Decode(bytes.NewReader(contentBytes))
+ if err != nil {
+ log.Err(err).Msg("failed to decode webp image")
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
+ draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
+ buf := new(bytes.Buffer)
+ err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ contentBytes := buf.Bytes()
+ log.Debug().Msg("uploading thumbnail file")
+ thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
+ Title: fmt.Sprintf("%s-thumb", title),
+ Content: bytes.NewReader(contentBytes),
+ })
+ if err != nil {
+ log.Err(err).Msg("failed to upload thumbnail file")
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ log.Debug().Msg("setting thumbnail file path in attachment")
+ att.SetPath(thumbnailFile)
+ case contentType == "image/avif":
+ log.Debug().Msg("creating thumbnail for avif file")
+ img, err := avif.Decode(bytes.NewReader(contentBytes))
+ if err != nil {
+ log.Err(err).Msg("failed to decode avif image")
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
+ draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
+ buf := new(bytes.Buffer)
+ err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
+ if err != nil {
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ contentBytes := buf.Bytes()
+ log.Debug().Msg("uploading thumbnail file")
+ thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
+ Title: fmt.Sprintf("%s-thumb", title),
+ Content: bytes.NewReader(contentBytes),
+ })
+ if err != nil {
+ log.Err(err).Msg("failed to upload thumbnail file")
+ err := tx.Rollback()
+ if err != nil {
+ return err
+ }
+ return err
+ }
+ log.Debug().Msg("setting thumbnail file path in attachment")
+ att.SetPath(thumbnailFile)
+ default:
+ return fmt.Errorf("file type %s is not supported for thumbnail creation or document thumnails disabled", title)
+ }
+
+ log.Debug().Msg("saving thumbnail attachment to database")
+ thumbnail, err := att.Save(ctx)
+ if err != nil {
+ return err
+ }
+
+ _, err = tx.Attachment.UpdateOneID(attachmentId).SetThumbnail(thumbnail).Save(ctx)
+ if err != nil {
+ return err
+ }
+
+ log.Debug().Msg("finishing thumbnail creation transaction")
+ if err := tx.Commit(); err != nil {
+ log.Err(err).Msg("failed to commit transaction")
+ return nil
+ }
+ return nil
+}
+
+func (r *AttachmentRepo) CreateMissingThumbnails(ctx context.Context, groupId uuid.UUID) (int, error) {
+ attachments, err := r.db.Attachment.Query().
+ Where(
+ attachment.HasItemWith(item.HasGroupWith(group.ID(groupId))),
+ attachment.TypeNEQ("thumbnail"),
+ ).
+ All(ctx)
+ if err != nil {
+ return 0, err
+ }
+
+ pubsubString, err := utils.GenerateSubPubConn(r.pubSubConn, "thumbnails")
+ if err != nil {
+ log.Err(err).Msg("failed to generate pubsub connection string")
+ }
+ topic, err := pubsub.OpenTopic(ctx, pubsubString)
+ if err != nil {
+ log.Err(err).Msg("failed to open pubsub topic")
+ }
+
+ count := 0
+ for _, attachment := range attachments {
+ if r.thumbnail.Enabled {
+ if !attachment.QueryThumbnail().ExistX(ctx) {
+ if count > 0 && count%100 == 0 {
+ time.Sleep(2 * time.Second)
+ }
+ err = topic.Send(ctx, &pubsub.Message{
+ Body: []byte(fmt.Sprintf("attachment_created:%s", attachment.ID.String())),
+ Metadata: map[string]string{
+ "group_id": groupId.String(),
+ "attachment_id": attachment.ID.String(),
+ "title": attachment.Title,
+ "path": attachment.Path,
+ },
+ })
+ if err != nil {
+ log.Err(err).Msg("failed to send message to topic")
+ continue
+ } else {
+ count++
+ }
+ }
+ }
+ }
+
+ return count, nil
+}
+
+func (r *AttachmentRepo) UploadFile(ctx context.Context, itemGroup *ent.Group, doc ItemCreateAttachment) (string, error) {
+ // Prepare for the hashing of the file contents
+ hashOut := make([]byte, 32)
+
+ // Read all content into a buffer
+ buf := new(bytes.Buffer)
+ _, err := io.Copy(buf, doc.Content)
+ if err != nil {
+ log.Err(err).Msg("failed to read file content")
+ return "", err
+ }
+ // Now the buffer contains all the data, use it for hashing
+ contentBytes := buf.Bytes()
+
+ // We use blake3 to generate a hash of the file contents, the group ID is used as context to ensure unique hashes
+ // for the same file across different groups to reduce the chance of collisions
+ // additionally, the hash can be used to validate the file contents if needed
+ blake3.DeriveKey(itemGroup.ID.String(), contentBytes, hashOut)
+
+ // Write the file to the blob storage bucket which might be a local file system or cloud storage
+ bucket, err := blob.OpenBucket(ctx, r.GetConnString())
+ if err != nil {
+ log.Err(err).Msg("failed to open bucket")
+ return "", err
+ }
+ defer func(bucket *blob.Bucket) {
+ err := bucket.Close()
+ if err != nil {
+ log.Err(err).Msg("failed to close bucket")
+ }
+ }(bucket)
+ md5hash := md5.New()
+ _, err = md5hash.Write(contentBytes)
+ if err != nil {
+ log.Err(err).Msg("failed to generate MD5 hash for storage")
+ return "", err
+ }
+ contentType := http.DetectContentType(contentBytes[:min(512, len(contentBytes))])
+ options := &blob.WriterOptions{
+ ContentType: contentType,
+ ContentMD5: md5hash.Sum(nil),
+ }
+ path := r.path(itemGroup.ID, fmt.Sprintf("%x", hashOut))
+ err = bucket.WriteAll(ctx, path, contentBytes, options)
+ if err != nil {
+ log.Err(err).Msg("failed to write file to bucket")
+ return "", err
+ }
+
+ return path, nil
+}
+
+func isImageFile(mimetype string) bool {
+ // Check file extension for image types
+ return strings.Contains(mimetype, "image/jpeg") || strings.Contains(mimetype, "image/png") || strings.Contains(mimetype, "image/gif")
+}
diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go
index 571eb916..99ea4522 100644
--- a/backend/internal/data/repo/repo_items.go
+++ b/backend/internal/data/repo/repo_items.go
@@ -134,7 +134,8 @@ type (
Location *LocationSummary `json:"location,omitempty" extensions:"x-nullable,x-omitempty"`
Labels []LabelSummary `json:"labels"`
- ImageID *uuid.UUID `json:"imageId,omitempty"`
+ ImageID *uuid.UUID `json:"imageId,omitempty" extensions:"x-nullable,x-omitempty"`
+ ThumbnailId *uuid.UUID `json:"thumbnailId,omitempty" extensions:"x-nullable,x-omitempty"`
// Sale details
SoldTime time.Time `json:"soldTime"`
@@ -189,10 +190,20 @@ func mapItemSummary(item *ent.Item) ItemSummary {
}
var imageID *uuid.UUID
+ var thumbnailID *uuid.UUID
if item.Edges.Attachments != nil {
for _, a := range item.Edges.Attachments {
if a.Primary && a.Type == attachment.TypePhoto {
imageID = &a.ID
+ if a.Edges.Thumbnail != nil {
+ if a.Edges.Thumbnail.ID != uuid.Nil {
+ thumbnailID = &a.Edges.Thumbnail.ID
+ } else {
+ thumbnailID = nil
+ }
+ } else {
+ thumbnailID = nil
+ }
break
}
}
@@ -215,8 +226,9 @@ func mapItemSummary(item *ent.Item) ItemSummary {
Labels: labels,
// Warranty
- Insured: item.Insured,
- ImageID: imageID,
+ Insured: item.Insured,
+ ImageID: imageID,
+ ThumbnailId: thumbnailID,
}
}
@@ -466,6 +478,7 @@ func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q Ite
aq.Where(
attachment.Primary(true),
)
+ aq.WithThumbnail()
})
if q.Page != -1 || q.PageSize != -1 {
diff --git a/backend/internal/data/repo/repos_all.go b/backend/internal/data/repo/repos_all.go
index 4325e217..d75cd03a 100644
--- a/backend/internal/data/repo/repos_all.go
+++ b/backend/internal/data/repo/repos_all.go
@@ -20,7 +20,7 @@ type AllRepos struct {
Notifiers *NotifierRepository
}
-func New(db *ent.Client, bus *eventbus.EventBus, storage config.Storage) *AllRepos {
+func New(db *ent.Client, bus *eventbus.EventBus, storage config.Storage, pubSubConn string, thumbnail config.Thumbnail) *AllRepos {
return &AllRepos{
Users: &UserRepository{db},
AuthTokens: &TokenRepository{db},
@@ -28,7 +28,7 @@ func New(db *ent.Client, bus *eventbus.EventBus, storage config.Storage) *AllRep
Locations: &LocationRepository{db, bus},
Labels: &LabelRepository{db, bus},
Items: &ItemsRepository{db, bus},
- Attachments: &AttachmentRepo{db, storage},
+ Attachments: &AttachmentRepo{db, storage, pubSubConn, thumbnail},
MaintEntry: &MaintenanceEntryRepository{db},
Notifiers: NewNotifierRepository(db),
}
diff --git a/backend/internal/sys/config/conf.go b/backend/internal/sys/config/conf.go
index 64ea0e09..c66218be 100644
--- a/backend/internal/sys/config/conf.go
+++ b/backend/internal/sys/config/conf.go
@@ -28,6 +28,7 @@ type Config struct {
Debug DebugConf `yaml:"debug"`
Options Options `yaml:"options"`
LabelMaker LabelMakerConf `yaml:"labelmaker"`
+ Thumbnail Thumbnail `yaml:"thumbnail"`
}
type Options struct {
@@ -38,6 +39,12 @@ type Options struct {
AllowAnalytics bool `yaml:"allow_analytics" conf:"default:false"`
}
+type Thumbnail struct {
+ Enabled bool `yaml:"enabled" conf:"default:true"`
+ Width int `yaml:"width" conf:"default:500"`
+ Height int `yaml:"height" conf:"default:500"`
+}
+
type DebugConf struct {
Enabled bool `yaml:"enabled" conf:"default:false"`
Port string `yaml:"port" conf:"default:4000"`
diff --git a/backend/internal/sys/config/conf_database.go b/backend/internal/sys/config/conf_database.go
index deff9740..4e65d112 100644
--- a/backend/internal/sys/config/conf_database.go
+++ b/backend/internal/sys/config/conf_database.go
@@ -11,12 +11,13 @@ type Storage struct {
}
type Database struct {
- Driver string `yaml:"driver" conf:"default:sqlite3"`
- Username string `yaml:"username"`
- Password string `yaml:"password"`
- Host string `yaml:"host"`
- Port string `yaml:"port"`
- Database string `yaml:"database"`
- SslMode string `yaml:"ssl_mode"`
- SqlitePath string `yaml:"sqlite_path" conf:"default:./.data/homebox.db?_pragma=busy_timeout=999&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite"`
+ Driver string `yaml:"driver" conf:"default:sqlite3"`
+ Username string `yaml:"username"`
+ Password string `yaml:"password"`
+ Host string `yaml:"host"`
+ Port string `yaml:"port"`
+ Database string `yaml:"database"`
+ SslMode string `yaml:"ssl_mode"`
+ SqlitePath string `yaml:"sqlite_path" conf:"default:./.data/homebox.db?_pragma=busy_timeout=999&_pragma=journal_mode=WAL&_fk=1&_time_format=sqlite"`
+ PubSubConnString string `yaml:"pubsub_conn_string" conf:"default:mem://{{ .Topic }}"`
}
diff --git a/backend/pkgs/utils/generator.go b/backend/pkgs/utils/generator.go
new file mode 100644
index 00000000..b026d3bc
--- /dev/null
+++ b/backend/pkgs/utils/generator.go
@@ -0,0 +1,27 @@
+// Package utils
+package utils
+
+import (
+ "fmt"
+ "strings"
+ "text/template"
+)
+
+// GenerateSubPubConn generates a subscription or publication connection string
+func GenerateSubPubConn(pubSubConn string, topic string) (string, error) {
+ if strings.Contains(topic, "{{") || strings.Contains(topic, "}}") {
+ return "", fmt.Errorf("topic contains template placeholders, which is not allowed")
+ }
+ builder := &strings.Builder{}
+ tmpl, err := template.New("subPubConn").Parse(pubSubConn)
+ if err != nil {
+ return "", fmt.Errorf("failed to parse template: %w", err)
+ }
+ err = tmpl.Execute(builder, map[string]interface{}{
+ "Topic": topic,
+ })
+ if err != nil {
+ return "", fmt.Errorf("failed to parse template: %w", err)
+ }
+ return builder.String(), nil
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 6b577608..06a72486 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,5 +7,14 @@ services:
args:
- COMMIT=head
- BUILD_TIME=0001-01-01T00:00:00Z
+ x-bake:
+ platforms:
+ - linux/amd64
+ - linux/arm64
+ - linux/arm/v7
+ - linux/riscv64
+ environment:
+ - HBOX_DEBUG=true
+ - HBOX_LOGGER_LEVEL=-1
ports:
- 3100:7745
diff --git a/docs/en/api/openapi-2.0.json b/docs/en/api/openapi-2.0.json
index 8e41e7b2..78260159 100644
--- a/docs/en/api/openapi-2.0.json
+++ b/docs/en/api/openapi-2.0.json
@@ -16,6 +16,31 @@
"host": "demo.homebox.software",
"basePath": "/api",
"paths": {
+ "/v1/actions/create-missing-thumbnails": {
+ "post": {
+ "security": [
+ {
+ "Bearer": []
+ }
+ ],
+ "description": "Creates thumbnails for items that are missing them",
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Actions"
+ ],
+ "summary": "Create Missing Thumbnails",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/v1.ActionAmountResult"
+ }
+ }
+ }
+ }
+ },
"/v1/actions/ensure-asset-ids": {
"post": {
"security": [
@@ -2094,6 +2119,42 @@
}
},
"definitions": {
+ "attachment.Type": {
+ "type": "string",
+ "enum": [
+ "attachment",
+ "photo",
+ "manual",
+ "warranty",
+ "attachment",
+ "receipt",
+ "thumbnail"
+ ],
+ "x-enum-varnames": [
+ "DefaultType",
+ "TypePhoto",
+ "TypeManual",
+ "TypeWarranty",
+ "TypeAttachment",
+ "TypeReceipt",
+ "TypeThumbnail"
+ ]
+ },
+ "authroles.Role": {
+ "type": "string",
+ "enum": [
+ "user",
+ "admin",
+ "user",
+ "attachments"
+ ],
+ "x-enum-varnames": [
+ "DefaultRole",
+ "RoleAdmin",
+ "RoleUser",
+ "RoleAttachments"
+ ]
+ },
"currencies.Currency": {
"type": "object",
"properties": {
@@ -2111,6 +2172,891 @@
}
}
},
+ "ent.Attachment": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AttachmentQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AttachmentEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "path": {
+ "description": "Path holds the value of the \"path\" field.",
+ "type": "string"
+ },
+ "primary": {
+ "description": "Primary holds the value of the \"primary\" field.",
+ "type": "boolean"
+ },
+ "title": {
+ "description": "Title holds the value of the \"title\" field.",
+ "type": "string"
+ },
+ "type": {
+ "description": "Type holds the value of the \"type\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/attachment.Type"
+ }
+ ]
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.AttachmentEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ },
+ "thumbnail": {
+ "description": "Thumbnail holds the value of the thumbnail edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Attachment"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthRoles": {
+ "type": "object",
+ "properties": {
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AuthRolesQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthRolesEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "integer"
+ },
+ "role": {
+ "description": "Role holds the value of the \"role\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/authroles.Role"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthRolesEdges": {
+ "type": "object",
+ "properties": {
+ "token": {
+ "description": "Token holds the value of the token edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthTokens"
+ }
+ ]
+ }
+ }
+ },
+ "ent.AuthTokens": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the AuthTokensQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthTokensEdges"
+ }
+ ]
+ },
+ "expires_at": {
+ "description": "ExpiresAt holds the value of the \"expires_at\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token holds the value of the \"token\" field.",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.AuthTokensEdges": {
+ "type": "object",
+ "properties": {
+ "roles": {
+ "description": "Roles holds the value of the roles edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.AuthRoles"
+ }
+ ]
+ },
+ "user": {
+ "description": "User holds the value of the user edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.User"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Group": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "currency": {
+ "description": "Currency holds the value of the \"currency\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the GroupQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.GroupEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.GroupEdges": {
+ "type": "object",
+ "properties": {
+ "invitation_tokens": {
+ "description": "InvitationTokens holds the value of the invitation_tokens edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.GroupInvitationToken"
+ }
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "labels": {
+ "description": "Labels holds the value of the labels edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Label"
+ }
+ },
+ "locations": {
+ "description": "Locations holds the value of the locations edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Location"
+ }
+ },
+ "notifiers": {
+ "description": "Notifiers holds the value of the notifiers edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Notifier"
+ }
+ },
+ "users": {
+ "description": "Users holds the value of the users edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.User"
+ }
+ }
+ }
+ },
+ "ent.GroupInvitationToken": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the GroupInvitationTokenQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.GroupInvitationTokenEdges"
+ }
+ ]
+ },
+ "expires_at": {
+ "description": "ExpiresAt holds the value of the \"expires_at\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token holds the value of the \"token\" field.",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "uses": {
+ "description": "Uses holds the value of the \"uses\" field.",
+ "type": "integer"
+ }
+ }
+ },
+ "ent.GroupInvitationTokenEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Item": {
+ "type": "object",
+ "properties": {
+ "archived": {
+ "description": "Archived holds the value of the \"archived\" field.",
+ "type": "boolean"
+ },
+ "asset_id": {
+ "description": "AssetID holds the value of the \"asset_id\" field.",
+ "type": "integer"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the ItemQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.ItemEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "import_ref": {
+ "description": "ImportRef holds the value of the \"import_ref\" field.",
+ "type": "string"
+ },
+ "insured": {
+ "description": "Insured holds the value of the \"insured\" field.",
+ "type": "boolean"
+ },
+ "lifetime_warranty": {
+ "description": "LifetimeWarranty holds the value of the \"lifetime_warranty\" field.",
+ "type": "boolean"
+ },
+ "manufacturer": {
+ "description": "Manufacturer holds the value of the \"manufacturer\" field.",
+ "type": "string"
+ },
+ "model_number": {
+ "description": "ModelNumber holds the value of the \"model_number\" field.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "notes": {
+ "description": "Notes holds the value of the \"notes\" field.",
+ "type": "string"
+ },
+ "purchase_from": {
+ "description": "PurchaseFrom holds the value of the \"purchase_from\" field.",
+ "type": "string"
+ },
+ "purchase_price": {
+ "description": "PurchasePrice holds the value of the \"purchase_price\" field.",
+ "type": "number"
+ },
+ "purchase_time": {
+ "description": "PurchaseTime holds the value of the \"purchase_time\" field.",
+ "type": "string"
+ },
+ "quantity": {
+ "description": "Quantity holds the value of the \"quantity\" field.",
+ "type": "integer"
+ },
+ "serial_number": {
+ "description": "SerialNumber holds the value of the \"serial_number\" field.",
+ "type": "string"
+ },
+ "sold_notes": {
+ "description": "SoldNotes holds the value of the \"sold_notes\" field.",
+ "type": "string"
+ },
+ "sold_price": {
+ "description": "SoldPrice holds the value of the \"sold_price\" field.",
+ "type": "number"
+ },
+ "sold_time": {
+ "description": "SoldTime holds the value of the \"sold_time\" field.",
+ "type": "string"
+ },
+ "sold_to": {
+ "description": "SoldTo holds the value of the \"sold_to\" field.",
+ "type": "string"
+ },
+ "sync_child_items_locations": {
+ "description": "SyncChildItemsLocations holds the value of the \"sync_child_items_locations\" field.",
+ "type": "boolean"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "warranty_details": {
+ "description": "WarrantyDetails holds the value of the \"warranty_details\" field.",
+ "type": "string"
+ },
+ "warranty_expires": {
+ "description": "WarrantyExpires holds the value of the \"warranty_expires\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.ItemEdges": {
+ "type": "object",
+ "properties": {
+ "attachments": {
+ "description": "Attachments holds the value of the attachments edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Attachment"
+ }
+ },
+ "children": {
+ "description": "Children holds the value of the children edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "fields": {
+ "description": "Fields holds the value of the fields edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.ItemField"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "label": {
+ "description": "Label holds the value of the label edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Label"
+ }
+ },
+ "location": {
+ "description": "Location holds the value of the location edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Location"
+ }
+ ]
+ },
+ "maintenance_entries": {
+ "description": "MaintenanceEntries holds the value of the maintenance_entries edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.MaintenanceEntry"
+ }
+ },
+ "parent": {
+ "description": "Parent holds the value of the parent edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.ItemField": {
+ "type": "object",
+ "properties": {
+ "boolean_value": {
+ "description": "BooleanValue holds the value of the \"boolean_value\" field.",
+ "type": "boolean"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the ItemFieldQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.ItemFieldEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "number_value": {
+ "description": "NumberValue holds the value of the \"number_value\" field.",
+ "type": "integer"
+ },
+ "text_value": {
+ "description": "TextValue holds the value of the \"text_value\" field.",
+ "type": "string"
+ },
+ "time_value": {
+ "description": "TimeValue holds the value of the \"time_value\" field.",
+ "type": "string"
+ },
+ "type": {
+ "description": "Type holds the value of the \"type\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/itemfield.Type"
+ }
+ ]
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.ItemFieldEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Label": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "description": "Color holds the value of the \"color\" field.",
+ "type": "string"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the LabelQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.LabelEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.LabelEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ }
+ }
+ },
+ "ent.Location": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the LocationQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.LocationEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.LocationEdges": {
+ "type": "object",
+ "properties": {
+ "children": {
+ "description": "Children holds the value of the children edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Location"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "items": {
+ "description": "Items holds the value of the items edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Item"
+ }
+ },
+ "parent": {
+ "description": "Parent holds the value of the parent edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Location"
+ }
+ ]
+ }
+ }
+ },
+ "ent.MaintenanceEntry": {
+ "type": "object",
+ "properties": {
+ "cost": {
+ "description": "Cost holds the value of the \"cost\" field.",
+ "type": "number"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "date": {
+ "description": "Date holds the value of the \"date\" field.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description holds the value of the \"description\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the MaintenanceEntryQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.MaintenanceEntryEdges"
+ }
+ ]
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "item_id": {
+ "description": "ItemID holds the value of the \"item_id\" field.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "scheduled_date": {
+ "description": "ScheduledDate holds the value of the \"scheduled_date\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.MaintenanceEntryEdges": {
+ "type": "object",
+ "properties": {
+ "item": {
+ "description": "Item holds the value of the item edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Item"
+ }
+ ]
+ }
+ }
+ },
+ "ent.Notifier": {
+ "type": "object",
+ "properties": {
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the NotifierQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.NotifierEdges"
+ }
+ ]
+ },
+ "group_id": {
+ "description": "GroupID holds the value of the \"group_id\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "is_active": {
+ "description": "IsActive holds the value of the \"is_active\" field.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ },
+ "user_id": {
+ "description": "UserID holds the value of the \"user_id\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.NotifierEdges": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "user": {
+ "description": "User holds the value of the user edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.User"
+ }
+ ]
+ }
+ }
+ },
+ "ent.User": {
+ "type": "object",
+ "properties": {
+ "activated_on": {
+ "description": "ActivatedOn holds the value of the \"activated_on\" field.",
+ "type": "string"
+ },
+ "created_at": {
+ "description": "CreatedAt holds the value of the \"created_at\" field.",
+ "type": "string"
+ },
+ "edges": {
+ "description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.UserEdges"
+ }
+ ]
+ },
+ "email": {
+ "description": "Email holds the value of the \"email\" field.",
+ "type": "string"
+ },
+ "id": {
+ "description": "ID of the ent.",
+ "type": "string"
+ },
+ "is_superuser": {
+ "description": "IsSuperuser holds the value of the \"is_superuser\" field.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "Name holds the value of the \"name\" field.",
+ "type": "string"
+ },
+ "role": {
+ "description": "Role holds the value of the \"role\" field.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/user.Role"
+ }
+ ]
+ },
+ "superuser": {
+ "description": "Superuser holds the value of the \"superuser\" field.",
+ "type": "boolean"
+ },
+ "updated_at": {
+ "description": "UpdatedAt holds the value of the \"updated_at\" field.",
+ "type": "string"
+ }
+ }
+ },
+ "ent.UserEdges": {
+ "type": "object",
+ "properties": {
+ "auth_tokens": {
+ "description": "AuthTokens holds the value of the auth_tokens edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.AuthTokens"
+ }
+ },
+ "group": {
+ "description": "Group holds the value of the group edge.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ent.Group"
+ }
+ ]
+ },
+ "notifiers": {
+ "description": "Notifiers holds the value of the notifiers edge.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ent.Notifier"
+ }
+ }
+ }
+ },
+ "itemfield.Type": {
+ "type": "string",
+ "enum": [
+ "text",
+ "number",
+ "boolean",
+ "time"
+ ],
+ "x-enum-varnames": [
+ "TypeText",
+ "TypeNumber",
+ "TypeBoolean",
+ "TypeTime"
+ ]
+ },
"repo.Group": {
"type": "object",
"properties": {
@@ -2180,6 +3126,9 @@
"primary": {
"type": "boolean"
},
+ "thumbnail": {
+ "$ref": "#/definitions/ent.Attachment"
+ },
"title": {
"type": "string"
},
@@ -2294,7 +3243,9 @@
"type": "string"
},
"imageId": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
},
"insured": {
"type": "boolean"
@@ -2373,6 +3324,11 @@
"syncChildItemsLocations": {
"type": "boolean"
},
+ "thumbnailId": {
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
+ },
"updatedAt": {
"type": "string"
},
@@ -2431,7 +3387,9 @@
"type": "string"
},
"imageId": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
},
"insured": {
"type": "boolean"
@@ -2465,6 +3423,11 @@
"description": "Sale details",
"type": "string"
},
+ "thumbnailId": {
+ "type": "string",
+ "x-nullable": true,
+ "x-omitempty": true
+ },
"updatedAt": {
"type": "string"
}
@@ -3095,6 +4058,19 @@
}
}
},
+ "user.Role": {
+ "type": "string",
+ "enum": [
+ "user",
+ "user",
+ "owner"
+ ],
+ "x-enum-varnames": [
+ "DefaultRole",
+ "RoleUser",
+ "RoleOwner"
+ ]
+ },
"v1.APISummary": {
"type": "object",
"properties": {
diff --git a/docs/en/api/openapi-2.0.yaml b/docs/en/api/openapi-2.0.yaml
index f807c315..db182f31 100644
--- a/docs/en/api/openapi-2.0.yaml
+++ b/docs/en/api/openapi-2.0.yaml
@@ -1,5 +1,35 @@
basePath: /api
definitions:
+ attachment.Type:
+ enum:
+ - attachment
+ - photo
+ - manual
+ - warranty
+ - attachment
+ - receipt
+ - thumbnail
+ type: string
+ x-enum-varnames:
+ - DefaultType
+ - TypePhoto
+ - TypeManual
+ - TypeWarranty
+ - TypeAttachment
+ - TypeReceipt
+ - TypeThumbnail
+ authroles.Role:
+ enum:
+ - user
+ - admin
+ - user
+ - attachments
+ type: string
+ x-enum-varnames:
+ - DefaultRole
+ - RoleAdmin
+ - RoleUser
+ - RoleAttachments
currencies.Currency:
properties:
code:
@@ -11,6 +41,608 @@ definitions:
symbol:
type: string
type: object
+ ent.Attachment:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.AttachmentEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the AttachmentQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ path:
+ description: Path holds the value of the "path" field.
+ type: string
+ primary:
+ description: Primary holds the value of the "primary" field.
+ type: boolean
+ title:
+ description: Title holds the value of the "title" field.
+ type: string
+ type:
+ allOf:
+ - $ref: '#/definitions/attachment.Type'
+ description: Type holds the value of the "type" field.
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.AttachmentEdges:
+ properties:
+ item:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Item holds the value of the item edge.
+ thumbnail:
+ allOf:
+ - $ref: '#/definitions/ent.Attachment'
+ description: Thumbnail holds the value of the thumbnail edge.
+ type: object
+ ent.AuthRoles:
+ properties:
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.AuthRolesEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the AuthRolesQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: integer
+ role:
+ allOf:
+ - $ref: '#/definitions/authroles.Role'
+ description: Role holds the value of the "role" field.
+ type: object
+ ent.AuthRolesEdges:
+ properties:
+ token:
+ allOf:
+ - $ref: '#/definitions/ent.AuthTokens'
+ description: Token holds the value of the token edge.
+ type: object
+ ent.AuthTokens:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.AuthTokensEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the AuthTokensQuery when eager-loading is set.
+ expires_at:
+ description: ExpiresAt holds the value of the "expires_at" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ token:
+ description: Token holds the value of the "token" field.
+ items:
+ type: integer
+ type: array
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.AuthTokensEdges:
+ properties:
+ roles:
+ allOf:
+ - $ref: '#/definitions/ent.AuthRoles'
+ description: Roles holds the value of the roles edge.
+ user:
+ allOf:
+ - $ref: '#/definitions/ent.User'
+ description: User holds the value of the user edge.
+ type: object
+ ent.Group:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ currency:
+ description: Currency holds the value of the "currency" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.GroupEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the GroupQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.GroupEdges:
+ properties:
+ invitation_tokens:
+ description: InvitationTokens holds the value of the invitation_tokens edge.
+ items:
+ $ref: '#/definitions/ent.GroupInvitationToken'
+ type: array
+ items:
+ description: Items holds the value of the items edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ labels:
+ description: Labels holds the value of the labels edge.
+ items:
+ $ref: '#/definitions/ent.Label'
+ type: array
+ locations:
+ description: Locations holds the value of the locations edge.
+ items:
+ $ref: '#/definitions/ent.Location'
+ type: array
+ notifiers:
+ description: Notifiers holds the value of the notifiers edge.
+ items:
+ $ref: '#/definitions/ent.Notifier'
+ type: array
+ users:
+ description: Users holds the value of the users edge.
+ items:
+ $ref: '#/definitions/ent.User'
+ type: array
+ type: object
+ ent.GroupInvitationToken:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.GroupInvitationTokenEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the GroupInvitationTokenQuery when eager-loading is set.
+ expires_at:
+ description: ExpiresAt holds the value of the "expires_at" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ token:
+ description: Token holds the value of the "token" field.
+ items:
+ type: integer
+ type: array
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ uses:
+ description: Uses holds the value of the "uses" field.
+ type: integer
+ type: object
+ ent.GroupInvitationTokenEdges:
+ properties:
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ type: object
+ ent.Item:
+ properties:
+ archived:
+ description: Archived holds the value of the "archived" field.
+ type: boolean
+ asset_id:
+ description: AssetID holds the value of the "asset_id" field.
+ type: integer
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.ItemEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the ItemQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ import_ref:
+ description: ImportRef holds the value of the "import_ref" field.
+ type: string
+ insured:
+ description: Insured holds the value of the "insured" field.
+ type: boolean
+ lifetime_warranty:
+ description: LifetimeWarranty holds the value of the "lifetime_warranty" field.
+ type: boolean
+ manufacturer:
+ description: Manufacturer holds the value of the "manufacturer" field.
+ type: string
+ model_number:
+ description: ModelNumber holds the value of the "model_number" field.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ notes:
+ description: Notes holds the value of the "notes" field.
+ type: string
+ purchase_from:
+ description: PurchaseFrom holds the value of the "purchase_from" field.
+ type: string
+ purchase_price:
+ description: PurchasePrice holds the value of the "purchase_price" field.
+ type: number
+ purchase_time:
+ description: PurchaseTime holds the value of the "purchase_time" field.
+ type: string
+ quantity:
+ description: Quantity holds the value of the "quantity" field.
+ type: integer
+ serial_number:
+ description: SerialNumber holds the value of the "serial_number" field.
+ type: string
+ sold_notes:
+ description: SoldNotes holds the value of the "sold_notes" field.
+ type: string
+ sold_price:
+ description: SoldPrice holds the value of the "sold_price" field.
+ type: number
+ sold_time:
+ description: SoldTime holds the value of the "sold_time" field.
+ type: string
+ sold_to:
+ description: SoldTo holds the value of the "sold_to" field.
+ type: string
+ sync_child_items_locations:
+ description: SyncChildItemsLocations holds the value of the "sync_child_items_locations"
+ field.
+ type: boolean
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ warranty_details:
+ description: WarrantyDetails holds the value of the "warranty_details" field.
+ type: string
+ warranty_expires:
+ description: WarrantyExpires holds the value of the "warranty_expires" field.
+ type: string
+ type: object
+ ent.ItemEdges:
+ properties:
+ attachments:
+ description: Attachments holds the value of the attachments edge.
+ items:
+ $ref: '#/definitions/ent.Attachment'
+ type: array
+ children:
+ description: Children holds the value of the children edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ fields:
+ description: Fields holds the value of the fields edge.
+ items:
+ $ref: '#/definitions/ent.ItemField'
+ type: array
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ label:
+ description: Label holds the value of the label edge.
+ items:
+ $ref: '#/definitions/ent.Label'
+ type: array
+ location:
+ allOf:
+ - $ref: '#/definitions/ent.Location'
+ description: Location holds the value of the location edge.
+ maintenance_entries:
+ description: MaintenanceEntries holds the value of the maintenance_entries
+ edge.
+ items:
+ $ref: '#/definitions/ent.MaintenanceEntry'
+ type: array
+ parent:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Parent holds the value of the parent edge.
+ type: object
+ ent.ItemField:
+ properties:
+ boolean_value:
+ description: BooleanValue holds the value of the "boolean_value" field.
+ type: boolean
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.ItemFieldEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the ItemFieldQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ number_value:
+ description: NumberValue holds the value of the "number_value" field.
+ type: integer
+ text_value:
+ description: TextValue holds the value of the "text_value" field.
+ type: string
+ time_value:
+ description: TimeValue holds the value of the "time_value" field.
+ type: string
+ type:
+ allOf:
+ - $ref: '#/definitions/itemfield.Type'
+ description: Type holds the value of the "type" field.
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.ItemFieldEdges:
+ properties:
+ item:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Item holds the value of the item edge.
+ type: object
+ ent.Label:
+ properties:
+ color:
+ description: Color holds the value of the "color" field.
+ type: string
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.LabelEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the LabelQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.LabelEdges:
+ properties:
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ items:
+ description: Items holds the value of the items edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ type: object
+ ent.Location:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.LocationEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the LocationQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.LocationEdges:
+ properties:
+ children:
+ description: Children holds the value of the children edge.
+ items:
+ $ref: '#/definitions/ent.Location'
+ type: array
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ items:
+ description: Items holds the value of the items edge.
+ items:
+ $ref: '#/definitions/ent.Item'
+ type: array
+ parent:
+ allOf:
+ - $ref: '#/definitions/ent.Location'
+ description: Parent holds the value of the parent edge.
+ type: object
+ ent.MaintenanceEntry:
+ properties:
+ cost:
+ description: Cost holds the value of the "cost" field.
+ type: number
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ date:
+ description: Date holds the value of the "date" field.
+ type: string
+ description:
+ description: Description holds the value of the "description" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.MaintenanceEntryEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the MaintenanceEntryQuery when eager-loading is set.
+ id:
+ description: ID of the ent.
+ type: string
+ item_id:
+ description: ItemID holds the value of the "item_id" field.
+ type: string
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ scheduled_date:
+ description: ScheduledDate holds the value of the "scheduled_date" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.MaintenanceEntryEdges:
+ properties:
+ item:
+ allOf:
+ - $ref: '#/definitions/ent.Item'
+ description: Item holds the value of the item edge.
+ type: object
+ ent.Notifier:
+ properties:
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.NotifierEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the NotifierQuery when eager-loading is set.
+ group_id:
+ description: GroupID holds the value of the "group_id" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ is_active:
+ description: IsActive holds the value of the "is_active" field.
+ type: boolean
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ user_id:
+ description: UserID holds the value of the "user_id" field.
+ type: string
+ type: object
+ ent.NotifierEdges:
+ properties:
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ user:
+ allOf:
+ - $ref: '#/definitions/ent.User'
+ description: User holds the value of the user edge.
+ type: object
+ ent.User:
+ properties:
+ activated_on:
+ description: ActivatedOn holds the value of the "activated_on" field.
+ type: string
+ created_at:
+ description: CreatedAt holds the value of the "created_at" field.
+ type: string
+ edges:
+ allOf:
+ - $ref: '#/definitions/ent.UserEdges'
+ description: |-
+ Edges holds the relations/edges for other nodes in the graph.
+ The values are being populated by the UserQuery when eager-loading is set.
+ email:
+ description: Email holds the value of the "email" field.
+ type: string
+ id:
+ description: ID of the ent.
+ type: string
+ is_superuser:
+ description: IsSuperuser holds the value of the "is_superuser" field.
+ type: boolean
+ name:
+ description: Name holds the value of the "name" field.
+ type: string
+ role:
+ allOf:
+ - $ref: '#/definitions/user.Role'
+ description: Role holds the value of the "role" field.
+ superuser:
+ description: Superuser holds the value of the "superuser" field.
+ type: boolean
+ updated_at:
+ description: UpdatedAt holds the value of the "updated_at" field.
+ type: string
+ type: object
+ ent.UserEdges:
+ properties:
+ auth_tokens:
+ description: AuthTokens holds the value of the auth_tokens edge.
+ items:
+ $ref: '#/definitions/ent.AuthTokens'
+ type: array
+ group:
+ allOf:
+ - $ref: '#/definitions/ent.Group'
+ description: Group holds the value of the group edge.
+ notifiers:
+ description: Notifiers holds the value of the notifiers edge.
+ items:
+ $ref: '#/definitions/ent.Notifier'
+ type: array
+ type: object
+ itemfield.Type:
+ enum:
+ - text
+ - number
+ - boolean
+ - time
+ type: string
+ x-enum-varnames:
+ - TypeText
+ - TypeNumber
+ - TypeBoolean
+ - TypeTime
repo.Group:
properties:
createdAt:
@@ -56,6 +688,8 @@ definitions:
type: string
primary:
type: boolean
+ thumbnail:
+ $ref: '#/definitions/ent.Attachment'
title:
type: string
type:
@@ -134,6 +768,8 @@ definitions:
type: string
imageId:
type: string
+ x-nullable: true
+ x-omitempty: true
insured:
type: boolean
labels:
@@ -185,6 +821,10 @@ definitions:
type: string
syncChildItemsLocations:
type: boolean
+ thumbnailId:
+ type: string
+ x-nullable: true
+ x-omitempty: true
updatedAt:
type: string
warrantyDetails:
@@ -225,6 +865,8 @@ definitions:
type: string
imageId:
type: string
+ x-nullable: true
+ x-omitempty: true
insured:
type: boolean
labels:
@@ -246,6 +888,10 @@ definitions:
soldTime:
description: Sale details
type: string
+ thumbnailId:
+ type: string
+ x-nullable: true
+ x-omitempty: true
updatedAt:
type: string
type: object
@@ -670,6 +1316,16 @@ definitions:
token:
type: string
type: object
+ user.Role:
+ enum:
+ - user
+ - user
+ - owner
+ type: string
+ x-enum-varnames:
+ - DefaultRole
+ - RoleUser
+ - RoleOwner
v1.APISummary:
properties:
allowRegistration:
@@ -779,6 +1435,21 @@ info:
title: Homebox API
version: "1.0"
paths:
+ /v1/actions/create-missing-thumbnails:
+ post:
+ description: Creates thumbnails for items that are missing them
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/v1.ActionAmountResult'
+ security:
+ - Bearer: []
+ summary: Create Missing Thumbnails
+ tags:
+ - Actions
/v1/actions/ensure-asset-ids:
post:
description: Ensures all items in the database have an asset ID
diff --git a/docs/en/configure/index.md b/docs/en/configure/index.md
index 54b7d134..369ba5e6 100644
--- a/docs/en/configure/index.md
+++ b/docs/en/configure/index.md
@@ -47,6 +47,9 @@ aside: false
| HBOX_LABEL_MAKER_PRINT_COMMAND | | the command to use for printing labels. if empty, label printing is disabled. `{{.FileName}}` 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_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 |
::: warning Security Considerations
For postgreSQL in production:
@@ -112,6 +115,9 @@ OPTIONS
--label-maker-print-command/$HBOX_LABEL_MAKER_PRINT_COMMAND
--label-maker-additional-information/$HBOX_LABEL_MAKER_DYNAMIC_LENGTH (default: true)
--label-maker-additional-information/$HBOX_LABEL_MAKER_ADDITIONAL_INFORMATION
+--thumbnail-enabled/$HBOX_THUMBNAIL_ENABLED (default: true)
+--thumbnail-width/$HBOX_THUMBNAIL_WIDTH (default: 500)
+--thumbnail-height/$HBOX_THUMBNAIL_HEIGHT (default: 500)
--help/-h display this help message
```
diff --git a/frontend/components/Item/Card.vue b/frontend/components/Item/Card.vue
index f38573eb..e4825e1f 100644
--- a/frontend/components/Item/Card.vue
+++ b/frontend/components/Item/Card.vue
@@ -69,8 +69,11 @@
if (!props.item.imageId) {
return "/no-image.jpg";
}
-
- return api.authURL(`/items/${props.item.id}/attachments/${props.item.imageId}`);
+ if (props.item.thumbnailId) {
+ return api.authURL(`/items/${props.item.id}/attachments/${props.item.thumbnailId}`);
+ } else {
+ return api.authURL(`/items/${props.item.id}/attachments/${props.item.imageId}`);
+ }
});
const top3 = computed(() => {
diff --git a/frontend/lib/api/classes/actions.ts b/frontend/lib/api/classes/actions.ts
index 3975a1d9..21466304 100644
--- a/frontend/lib/api/classes/actions.ts
+++ b/frontend/lib/api/classes/actions.ts
@@ -25,4 +25,10 @@ export class ActionsAPI extends BaseAPI {
url: route("/actions/set-primary-photos"),
});
}
+
+ createMissingThumbnails() {
+ return this.http.post({
+ url: route("/actions/create-missing-thumbnails"),
+ });
+ }
}
diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts
index b85d5a3d..e16d753b 100644
--- a/frontend/lib/api/types/data-contracts.ts
+++ b/frontend/lib/api/types/data-contracts.ts
@@ -11,6 +11,12 @@
* ---------------------------------------------------------------
*/
+export enum UserRole {
+ DefaultRole = "user",
+ RoleUser = "user",
+ RoleOwner = "owner",
+}
+
export enum MaintenanceFilterStatus {
MaintenanceFilterStatusScheduled = "scheduled",
MaintenanceFilterStatusCompleted = "completed",
@@ -22,6 +28,30 @@ export enum ItemType {
ItemTypeItem = "item",
}
+export enum ItemfieldType {
+ TypeText = "text",
+ TypeNumber = "number",
+ TypeBoolean = "boolean",
+ TypeTime = "time",
+}
+
+export enum AuthrolesRole {
+ DefaultRole = "user",
+ RoleAdmin = "admin",
+ RoleUser = "user",
+ RoleAttachments = "attachments",
+}
+
+export enum AttachmentType {
+ DefaultType = "attachment",
+ TypePhoto = "photo",
+ TypeManual = "manual",
+ TypeWarranty = "warranty",
+ TypeAttachment = "attachment",
+ TypeReceipt = "receipt",
+ TypeThumbnail = "thumbnail",
+}
+
export interface CurrenciesCurrency {
code: string;
local: string;
@@ -29,6 +59,396 @@ export interface CurrenciesCurrency {
symbol: string;
}
+export interface EntAttachment {
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the AttachmentQuery when eager-loading is set.
+ */
+ edges: EntAttachmentEdges;
+ /** ID of the ent. */
+ id: string;
+ /** Path holds the value of the "path" field. */
+ path: string;
+ /** Primary holds the value of the "primary" field. */
+ primary: boolean;
+ /** Title holds the value of the "title" field. */
+ title: string;
+ /** Type holds the value of the "type" field. */
+ type: AttachmentType;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntAttachmentEdges {
+ /** Item holds the value of the item edge. */
+ item: EntItem;
+ /** Thumbnail holds the value of the thumbnail edge. */
+ thumbnail: EntAttachment;
+}
+
+export interface EntAuthRoles {
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the AuthRolesQuery when eager-loading is set.
+ */
+ edges: EntAuthRolesEdges;
+ /** ID of the ent. */
+ id: number;
+ /** Role holds the value of the "role" field. */
+ role: AuthrolesRole;
+}
+
+export interface EntAuthRolesEdges {
+ /** Token holds the value of the token edge. */
+ token: EntAuthTokens;
+}
+
+export interface EntAuthTokens {
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the AuthTokensQuery when eager-loading is set.
+ */
+ edges: EntAuthTokensEdges;
+ /** ExpiresAt holds the value of the "expires_at" field. */
+ expires_at: string;
+ /** ID of the ent. */
+ id: string;
+ /** Token holds the value of the "token" field. */
+ token: number[];
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntAuthTokensEdges {
+ /** Roles holds the value of the roles edge. */
+ roles: EntAuthRoles;
+ /** User holds the value of the user edge. */
+ user: EntUser;
+}
+
+export interface EntGroup {
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /** Currency holds the value of the "currency" field. */
+ currency: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the GroupQuery when eager-loading is set.
+ */
+ edges: EntGroupEdges;
+ /** ID of the ent. */
+ id: string;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntGroupEdges {
+ /** InvitationTokens holds the value of the invitation_tokens edge. */
+ invitation_tokens: EntGroupInvitationToken[];
+ /** Items holds the value of the items edge. */
+ items: EntItem[];
+ /** Labels holds the value of the labels edge. */
+ labels: EntLabel[];
+ /** Locations holds the value of the locations edge. */
+ locations: EntLocation[];
+ /** Notifiers holds the value of the notifiers edge. */
+ notifiers: EntNotifier[];
+ /** Users holds the value of the users edge. */
+ users: EntUser[];
+}
+
+export interface EntGroupInvitationToken {
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the GroupInvitationTokenQuery when eager-loading is set.
+ */
+ edges: EntGroupInvitationTokenEdges;
+ /** ExpiresAt holds the value of the "expires_at" field. */
+ expires_at: string;
+ /** ID of the ent. */
+ id: string;
+ /** Token holds the value of the "token" field. */
+ token: number[];
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+ /** Uses holds the value of the "uses" field. */
+ uses: number;
+}
+
+export interface EntGroupInvitationTokenEdges {
+ /** Group holds the value of the group edge. */
+ group: EntGroup;
+}
+
+export interface EntItem {
+ /** Archived holds the value of the "archived" field. */
+ archived: boolean;
+ /** AssetID holds the value of the "asset_id" field. */
+ asset_id: number;
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /** Description holds the value of the "description" field. */
+ description: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the ItemQuery when eager-loading is set.
+ */
+ edges: EntItemEdges;
+ /** ID of the ent. */
+ id: string;
+ /** ImportRef holds the value of the "import_ref" field. */
+ import_ref: string;
+ /** Insured holds the value of the "insured" field. */
+ insured: boolean;
+ /** LifetimeWarranty holds the value of the "lifetime_warranty" field. */
+ lifetime_warranty: boolean;
+ /** Manufacturer holds the value of the "manufacturer" field. */
+ manufacturer: string;
+ /** ModelNumber holds the value of the "model_number" field. */
+ model_number: string;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** Notes holds the value of the "notes" field. */
+ notes: string;
+ /** PurchaseFrom holds the value of the "purchase_from" field. */
+ purchase_from: string;
+ /** PurchasePrice holds the value of the "purchase_price" field. */
+ purchase_price: number;
+ /** PurchaseTime holds the value of the "purchase_time" field. */
+ purchase_time: string;
+ /** Quantity holds the value of the "quantity" field. */
+ quantity: number;
+ /** SerialNumber holds the value of the "serial_number" field. */
+ serial_number: string;
+ /** SoldNotes holds the value of the "sold_notes" field. */
+ sold_notes: string;
+ /** SoldPrice holds the value of the "sold_price" field. */
+ sold_price: number;
+ /** SoldTime holds the value of the "sold_time" field. */
+ sold_time: string;
+ /** SoldTo holds the value of the "sold_to" field. */
+ sold_to: string;
+ /** SyncChildItemsLocations holds the value of the "sync_child_items_locations" field. */
+ sync_child_items_locations: boolean;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+ /** WarrantyDetails holds the value of the "warranty_details" field. */
+ warranty_details: string;
+ /** WarrantyExpires holds the value of the "warranty_expires" field. */
+ warranty_expires: string;
+}
+
+export interface EntItemEdges {
+ /** Attachments holds the value of the attachments edge. */
+ attachments: EntAttachment[];
+ /** Children holds the value of the children edge. */
+ children: EntItem[];
+ /** Fields holds the value of the fields edge. */
+ fields: EntItemField[];
+ /** Group holds the value of the group edge. */
+ group: EntGroup;
+ /** Label holds the value of the label edge. */
+ label: EntLabel[];
+ /** Location holds the value of the location edge. */
+ location: EntLocation;
+ /** MaintenanceEntries holds the value of the maintenance_entries edge. */
+ maintenance_entries: EntMaintenanceEntry[];
+ /** Parent holds the value of the parent edge. */
+ parent: EntItem;
+}
+
+export interface EntItemField {
+ /** BooleanValue holds the value of the "boolean_value" field. */
+ boolean_value: boolean;
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /** Description holds the value of the "description" field. */
+ description: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the ItemFieldQuery when eager-loading is set.
+ */
+ edges: EntItemFieldEdges;
+ /** ID of the ent. */
+ id: string;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** NumberValue holds the value of the "number_value" field. */
+ number_value: number;
+ /** TextValue holds the value of the "text_value" field. */
+ text_value: string;
+ /** TimeValue holds the value of the "time_value" field. */
+ time_value: string;
+ /** Type holds the value of the "type" field. */
+ type: ItemfieldType;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntItemFieldEdges {
+ /** Item holds the value of the item edge. */
+ item: EntItem;
+}
+
+export interface EntLabel {
+ /** Color holds the value of the "color" field. */
+ color: string;
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /** Description holds the value of the "description" field. */
+ description: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the LabelQuery when eager-loading is set.
+ */
+ edges: EntLabelEdges;
+ /** ID of the ent. */
+ id: string;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntLabelEdges {
+ /** Group holds the value of the group edge. */
+ group: EntGroup;
+ /** Items holds the value of the items edge. */
+ items: EntItem[];
+}
+
+export interface EntLocation {
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /** Description holds the value of the "description" field. */
+ description: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the LocationQuery when eager-loading is set.
+ */
+ edges: EntLocationEdges;
+ /** ID of the ent. */
+ id: string;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntLocationEdges {
+ /** Children holds the value of the children edge. */
+ children: EntLocation[];
+ /** Group holds the value of the group edge. */
+ group: EntGroup;
+ /** Items holds the value of the items edge. */
+ items: EntItem[];
+ /** Parent holds the value of the parent edge. */
+ parent: EntLocation;
+}
+
+export interface EntMaintenanceEntry {
+ /** Cost holds the value of the "cost" field. */
+ cost: number;
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /** Date holds the value of the "date" field. */
+ date: Date | string;
+ /** Description holds the value of the "description" field. */
+ description: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the MaintenanceEntryQuery when eager-loading is set.
+ */
+ edges: EntMaintenanceEntryEdges;
+ /** ID of the ent. */
+ id: string;
+ /** ItemID holds the value of the "item_id" field. */
+ item_id: string;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** ScheduledDate holds the value of the "scheduled_date" field. */
+ scheduled_date: Date | string;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntMaintenanceEntryEdges {
+ /** Item holds the value of the item edge. */
+ item: EntItem;
+}
+
+export interface EntNotifier {
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the NotifierQuery when eager-loading is set.
+ */
+ edges: EntNotifierEdges;
+ /** GroupID holds the value of the "group_id" field. */
+ group_id: string;
+ /** ID of the ent. */
+ id: string;
+ /** IsActive holds the value of the "is_active" field. */
+ is_active: boolean;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+ /** UserID holds the value of the "user_id" field. */
+ user_id: string;
+}
+
+export interface EntNotifierEdges {
+ /** Group holds the value of the group edge. */
+ group: EntGroup;
+ /** User holds the value of the user edge. */
+ user: EntUser;
+}
+
+export interface EntUser {
+ /** ActivatedOn holds the value of the "activated_on" field. */
+ activated_on: string;
+ /** CreatedAt holds the value of the "created_at" field. */
+ created_at: string;
+ /**
+ * Edges holds the relations/edges for other nodes in the graph.
+ * The values are being populated by the UserQuery when eager-loading is set.
+ */
+ edges: EntUserEdges;
+ /** Email holds the value of the "email" field. */
+ email: string;
+ /** ID of the ent. */
+ id: string;
+ /** IsSuperuser holds the value of the "is_superuser" field. */
+ is_superuser: boolean;
+ /** Name holds the value of the "name" field. */
+ name: string;
+ /** Role holds the value of the "role" field. */
+ role: UserRole;
+ /** Superuser holds the value of the "superuser" field. */
+ superuser: boolean;
+ /** UpdatedAt holds the value of the "updated_at" field. */
+ updated_at: string;
+}
+
+export interface EntUserEdges {
+ /** AuthTokens holds the value of the auth_tokens edge. */
+ auth_tokens: EntAuthTokens[];
+ /** Group holds the value of the group edge. */
+ group: EntGroup;
+ /** Notifiers holds the value of the notifiers edge. */
+ notifiers: EntNotifier[];
+}
+
export interface Group {
createdAt: Date | string;
currency: string;
@@ -56,6 +476,7 @@ export interface ItemAttachment {
id: string;
path: string;
primary: boolean;
+ thumbnail: EntAttachment;
title: string;
type: string;
updatedAt: Date | string;
@@ -100,7 +521,7 @@ export interface ItemOut {
description: string;
fields: ItemField[];
id: string;
- imageId: string;
+ imageId?: string | null;
insured: boolean;
labels: LabelSummary[];
/** Warranty */
@@ -125,6 +546,7 @@ export interface ItemOut {
soldTime: Date | string;
soldTo: string;
syncChildItemsLocations: boolean;
+ thumbnailId?: string | null;
updatedAt: Date | string;
warrantyDetails: string;
warrantyExpires: Date | string;
@@ -148,7 +570,7 @@ export interface ItemSummary {
createdAt: Date | string;
description: string;
id: string;
- imageId: string;
+ imageId?: string | null;
insured: boolean;
labels: LabelSummary[];
/** Edges */
@@ -158,6 +580,7 @@ export interface ItemSummary {
quantity: number;
/** Sale details */
soldTime: Date | string;
+ thumbnailId?: string | null;
updatedAt: Date | string;
}
diff --git a/frontend/locales/en.json b/frontend/locales/en.json
index e48c1eea..aaa08b46 100644
--- a/frontend/locales/en.json
+++ b/frontend/locales/en.json
@@ -576,7 +576,12 @@
"zero_datetimes": "Zero Item Date Times",
"zero_datetimes_button": "Zero Item Date Times",
"zero_datetimes_confirm": "Are you sure you want to reset all date and time values? This can take a while and cannot be undone.",
- "zero_datetimes_sub": "Resets the time value for all date time fields in your inventory to the beginning of the date. This is to fix a bug that was introduced early on in the development of the site that caused the time value to be stored with the time which caused issues with date fields displaying accurate values. ''See Github Issue #236 for more details.''"
+ "zero_datetimes_sub": "Resets the time value for all date time fields in your inventory to the beginning of the date. This is to fix a bug that was introduced early on in the development of the site that caused the time value to be stored with the time which caused issues with date fields displaying accurate values. ''See Github Issue #236 for more details.''",
+ "create_missing_thumbnails": "Create Missing Thumbnails",
+ "create_missing_thumbnails_sub": "Creates thumbnails for all attachments that are supported by the current configuration. This is useful for attachments that were uploaded prior to the v0.20.0 release of Homebox. This will not overwrite existing thumbnails, only create new ones for attachments that do not have a thumbnail. Please note that the thumbnails are created in the background and may take a while to complete.",
+ "create_missing_thumbnails_button": "Create Thumbnails",
+ "create_missing_thumbnails_confirm": "Are you sure you want to create missing thumbnails? This can take a while and cannot be paused."
+
},
"actions_sub": "Apply Actions to your inventory in bulk. These are irreversible actions. ''Be careful.''",
"import_export": "Import/Export",
@@ -605,7 +610,8 @@
"failed_ensure_ids": "Failed to ensure asset IDs.",
"failed_ensure_import_refs": "Failed to ensure import refs.",
"failed_set_primary_photos": "Failed to set primary photos.",
- "failed_zero_datetimes": "Failed to reset date and time values."
+ "failed_zero_datetimes": "Failed to reset date and time values.",
+ "failed_create_missing_thumbnails": "Failed to create missing thumbnails."
}
}
}
diff --git a/frontend/pages/item/[id]/index/edit.vue b/frontend/pages/item/[id]/index/edit.vue
index 5aecee6d..c4fdcf22 100644
--- a/frontend/pages/item/[id]/index/edit.vue
+++ b/frontend/pages/item/[id]/index/edit.vue
@@ -127,19 +127,16 @@
toast.success(t("items.toast.item_saved"));
navigateTo("/item/" + itemId.value);
}
- type NoUndefinedField = { [P in keyof T]-?: NoUndefinedField> };
- type StringKeys = { [k in keyof T]: T[k] extends string ? k : never }[keyof T];
- type OnlyString = { [k in StringKeys]: string };
-
- type NumberKeys = { [k in keyof T]: T[k] extends number ? k : never }[keyof T];
- type OnlyNumber = { [k in NumberKeys]: number };
+ type NonNullableStringKeys = Extract;
+ type NonNullableNumberKeys = Extract;
+ type BooleanKeys = Extract;
+ type DateKeys = Extract;
type TextFormField = {
type: "text" | "textarea";
label: string;
- // key of ItemOut where the value is a string
- ref: keyof OnlyString>;
+ ref: NonNullableStringKeys;
maxLength?: number;
minLength?: number;
};
@@ -147,27 +144,19 @@
type NumberFormField = {
type: "number";
label: string;
- ref: keyof OnlyNumber> | keyof OnlyString>;
+ ref: NonNullableNumberKeys | NonNullableStringKeys;
};
- // https://stackoverflow.com/questions/50851263/how-do-i-require-a-keyof-to-be-for-a-property-of-a-specific-type
- // I don't know why typescript can't just be normal
- type BooleanKeys = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T];
- type OnlyBoolean = { [k in BooleanKeys]: boolean };
-
interface BoolFormField {
type: "checkbox";
label: string;
- ref: keyof OnlyBoolean>;
+ ref: BooleanKeys;
}
- type DateKeys = { [k in keyof T]: T[k] extends Date | string ? k : never }[keyof T];
- type OnlyDate = { [k in DateKeys]: Date | string };
-
type DateFormField = {
type: "date";
label: string;
- ref: keyof OnlyDate>;
+ ref: DateKeys;
};
type FormField = TextFormField | BoolFormField | DateFormField | NumberFormField;
@@ -247,7 +236,6 @@
{
type: "date",
label: "items.purchase_date",
- // @ts-expect-error - we know this is a date
ref: "purchaseTime",
},
];
@@ -261,7 +249,6 @@
{
type: "date",
label: "items.warranty_expires",
- // @ts-expect-error - we know this is a date
ref: "warrantyExpires",
},
{
@@ -287,7 +274,6 @@
{
type: "date",
label: "items.sold_at",
- // @ts-expect-error - we know this is a date
ref: "soldTime",
},
];
diff --git a/frontend/pages/tools.vue b/frontend/pages/tools.vue
index 1c130210..7fb0ac3d 100644
--- a/frontend/pages/tools.vue
+++ b/frontend/pages/tools.vue
@@ -84,6 +84,12 @@
{{ $t("tools.actions_set.set_primary_photo_button") }}
+
+ {{ $t("tools.actions_set.create_missing_thumbnails") }}
+
+
+ {{ $t("tools.actions_set.create_missing_thumbnails_button") }}
+
@@ -141,6 +147,23 @@
toast.success(t("tools.toast.asset_success", { results: result.data.completed }));
}
+ async function createMissingThumbnails() {
+ const { isCanceled } = await confirm.open(t("tools.actions_set.create_missing_thumbnails_confirm"));
+
+ if (isCanceled) {
+ return;
+ }
+
+ const result = await api.actions.createMissingThumbnails();
+
+ if (result.error) {
+ toast.error(t("tools.toast.failed_create_missing_thumbnails"));
+ return;
+ }
+
+ toast.success(t("tools.toast.asset_success", { results: result.data.completed }));
+ }
+
async function ensureImportRefs() {
const { isCanceled } = await confirm.open(t("tools.import_export_set.import_ref_confirm"));