Compare commits

...

73 Commits

Author SHA1 Message Date
Tonya
a4b2f309b5 Merge branch 'main' into tonya/convert-labels-to-tags 2026-01-03 12:16:29 +00:00
dependabot[bot]
10ce256b6a Bump the npm_and_yarn group across 2 directories with 1 update (#1203)
Bumps the npm_and_yarn group with 1 update in the / directory: [qs](https://github.com/ljharb/qs).
Bumps the npm_and_yarn group with 1 update in the /frontend directory: [qs](https://github.com/ljharb/qs).


Updates `qs` from 6.14.0 to 6.14.1
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.1)

Updates `qs` from 6.14.0 to 6.14.1
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 21:11:11 -05:00
Matthew Kilgore
cb2c6ef8f2 Stop publishing sha releases with tags 2026-01-01 21:10:12 -05:00
Matt
abad8ad1ee Add pikapods hosting 2025-12-31 08:50:50 -05:00
tonyaellie
89863b199e fix: oops forgot 2025-12-30 12:33:47 +00:00
tonyaellie
f272743957 feat: migrations 2025-12-30 12:32:51 +00:00
tonyaellie
c2a9664e3e fix: wipe test 2025-12-30 11:46:31 +00:00
Tonya
4b938d639b Merge branch 'main' into tonya/convert-labels-to-tags 2025-12-30 11:44:51 +00:00
Phil
b02b39c1b3 Refactor wipe inventory E2E playwright tests 2025-12-29 16:28:41 +00:00
Phil
b290175bb0 Refactor Wipe Inventory E2E tests
Initial commit
2025-12-29 16:16:20 +00:00
tonyaellie
7c6cc152f6 feat: progess 2025-12-29 10:21:46 +00:00
tonyaellie
959199b8f4 feat: frontend done 2025-12-28 23:10:09 +00:00
tonyaellie
60e54b75c6 tonya/convert-labels-to-tags 2025-12-28 21:53:04 +00:00
Phil
e4aa38b264 Merge pull request #1186 from sysadminsmedia/copilot/fix-wipe-inventory-issue 2025-12-28 20:31:41 +00:00
Phil
e60f005990 Merge branch 'main' into copilot/fix-wipe-inventory-issue 2025-12-28 20:31:06 +00:00
Copilot
7dfaa0298b Add repository onboarding instructions for Copilot coding agents (#1187)
* Initial plan

* Initial plan for repository onboarding instructions

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

* Add comprehensive coding agent instructions for Homebox repository

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

* Optimize instructions to meet 2-page limit while maintaining completeness

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

* Add 4 path-specific instruction files for backend and frontend architecture

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

* Path application for instructions

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
Co-authored-by: Matt <tankerkiller125@users.noreply.github.com>
2025-12-28 15:03:51 -05:00
Phil
fbe7382acd Merge branch 'main' into copilot/fix-wipe-inventory-issue 2025-12-28 19:27:00 +00:00
copilot-swe-agent[bot]
1003223b47 Add E2E test for wipe inventory functionality
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 19:24:14 +00:00
Logan Miller
3c532896f5 fix(items): update quantity display immediately on increment/decrement (#1181)
Use API response data to update item state instead of directly mutating the quantity property.
Fixes #1180
2025-12-28 19:22:21 +00:00
copilot-swe-agent[bot]
4ba1a263c8 Publish mutation events when wiping labels and locations
Co-authored-by: tonyaellie <46281725+tonyaellie@users.noreply.github.com>
2025-12-28 19:06:04 +00:00
copilot-swe-agent[bot]
94f0123d9c Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com> 2025-12-28 18:19:00 +00:00
copilot-swe-agent[bot]
1f6782f8be Final documentation update
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 18:12:23 +00:00
copilot-swe-agent[bot]
ec8703114f Reset isConfirming flag after dialog closes
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 18:08:54 +00:00
copilot-swe-agent[bot]
5cd7792701 Fix callback order and restore isConfirming flag
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 18:05:29 +00:00
copilot-swe-agent[bot]
d82c52df26 Fix wipe inventory by replacing AlertDialogAction with Button
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 18:02:08 +00:00
copilot-swe-agent[bot]
033c17552b Update plan with root cause analysis
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 18:01:24 +00:00
copilot-swe-agent[bot]
2355438962 Initial plan 2025-12-28 17:45:18 +00:00
Phil
2a6773d1d6 Fix wipe inventory action to use correct onclose 2025-12-28 17:29:14 +00:00
copilot-swe-agent[bot]
c8c07e2878 Fix code review nitpick: remove empty comment line
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 17:27:01 +00:00
copilot-swe-agent[bot]
a3c05c3497 Fix WipeInventoryDialog to use DialogProvider onClose correctly
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 17:22:57 +00:00
copilot-swe-agent[bot]
ab0647fe68 Add comprehensive integration tests for wipe inventory
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 17:20:17 +00:00
copilot-swe-agent[bot]
0b616225a6 Code review and security checks complete
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 17:08:07 +00:00
copilot-swe-agent[bot]
dc9c7b76f2 Add tests for WipeInventory functionality
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 17:05:10 +00:00
copilot-swe-agent[bot]
b99102e093 Fix wipe inventory: delete maintenance records before items
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 17:01:12 +00:00
copilot-swe-agent[bot]
3077602f93 Initial plan 2025-12-28 16:50:34 +00:00
Phil
2bd6ff580a Add option to wipe inventory 2025-12-28 16:32:53 +00:00
copilot-swe-agent[bot]
35941583c8 Fix frontend linting errors in WipeInventoryDialog
- Replaced BaseModal and BaseButton with AlertDialog components from ui library
- Added proper imports for AlertDialog, AlertDialogContent, AlertDialogHeader, etc.
- Fixed prettier formatting issues (auto-fixed by eslint --fix)
- Fixed Tailwind CSS shorthand (h-4 w-4 -> size-4)
- Added addAlert/removeAlert for dialog provider integration
- All linting and type-checking errors resolved

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 16:26:09 +00:00
copilot-swe-agent[bot]
d576c89c7e Add maintenance records checkbox to wipe inventory dialog
- Added wipeMaintenance parameter to backend WipeInventory method
- Added checkbox in WipeInventoryDialog for wiping maintenance records
- Updated demo mode error message to include maintenance records
- Added translation key "wipe_inventory_maintenance" to en.json
- Maintenance records are linked to items via HasItemWith query
- Follows same structure as labels and locations checkboxes

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 16:08:33 +00:00
copilot-swe-agent[bot]
ff355f3cd8 Add demo mode check for wipe inventory action
Added frontend check to display error modal when user attempts to wipe inventory in demo mode. The modal shows: "Inventory, labels and locations cannot be wiped whilst Homebox is in demo mode. Please ensure that you are not in demo mode and try again."

Backend already had demo mode protection returning 403 Forbidden.

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 15:56:42 +00:00
copilot-swe-agent[bot]
03dc7fa841 Include label and location counts in wipe inventory total
Added labelCount and locationCount to the deleted counter to provide accurate total count of all deleted entities (items, labels, locations) in the wipe inventory result.

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 15:45:49 +00:00
copilot-swe-agent[bot]
7aaaa346ab Add wipe inventory options for labels/locations and owner-only restriction
- Added WipeInventoryDialog component with checkboxes for wiping labels and locations
- Modified backend WipeInventory method to accept wipeLabels and wipeLocations parameters
- Added owner check in HandleWipeInventory to restrict action to group owners only
- Updated frontend API client to send wipe options
- Added new translation keys for checkbox labels and owner note
- Integrated dialog into app layout and updated tools.vue to use new dialog

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-28 15:41:13 +00:00
Phil
27309e61da Add error import to v1_ctrl_actions.go 2025-12-28 15:29:14 +00:00
Phil
61816acdaa Merge branch 'main' into copilot/add-wipe-inventory-action 2025-12-28 15:28:07 +00:00
Phil
c31410727b Update backend/app/api/handlers/v1/v1_ctrl_actions.go
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-28 15:19:19 +00:00
Matt
4557df86ed Remove 32bit builds (#1000)
* Remove 32bit builds

* Use native Github runners

* Fix arm builds not getting runner

* Fix lint job
2025-12-28 00:28:20 -05:00
Matthew Kilgore
b8910f1b21 This should wipe out action related security flags 2025-12-27 19:09:27 -05:00
Matthew Kilgore
48e4f8da2a That should be all the actions pinned 2025-12-27 19:05:33 -05:00
Matthew Kilgore
1e0158c27e Fix copilot action 2025-12-27 18:52:12 -05:00
Matthew Kilgore
4fb3ddd661 Pin github copilot actions 2025-12-27 18:51:12 -05:00
Matthew Kilgore
690005de06 Harden all github actions 2025-12-27 18:46:14 -05:00
copilot-swe-agent[bot]
23da976494 Remove translation changes from non-English locale files
Only en.json should have the new translation keys. Translators will add translations to other language files later.

Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
2025-12-27 22:29:18 +00:00
Harrison Conlin
f0b8bb8b7f refactor(backend): use constants for database driver names (#1177)
magic constants are bad m'kay
2025-12-27 16:16:48 -05:00
Katos
ecc9fa1959 Disable triggers in upgrade-test.yaml
Comment out the workflow triggers in upgrade-test.yaml
2025-12-27 20:28:31 +00:00
Katos
7068a85dfb Merge pull request #1178 from sysadminsmedia/copilot/create-ci-cd-pipeline
Add CI/CD workflow for upgrade testing with data integrity verification
2025-12-27 20:18:37 +00:00
Katos
c73922c754 Merge branch 'main' into copilot/create-ci-cd-pipeline 2025-12-27 20:16:41 +00:00
Matthew Kilgore
ae2179c01c Add blog link 2025-12-27 12:05:05 -05:00
copilot-swe-agent[bot]
09e056a3fb Move upgrade verification tests to separate directory
- Move upgrade-verification.spec.ts from test/e2e/ to test/upgrade/
- This prevents the test from running during normal E2E CI runs
- The upgrade test is only meant for the upgrade-test workflow
- Update workflow and documentation to reflect new location

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 16:12:51 +00:00
Katos
4abfc76865 Fix CodeRabbit date quoting issue 2025-12-27 16:01:57 +00:00
copilot-swe-agent[bot]
aa48c958d7 Improve error handling and comments in WipeInventory method
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 15:03:43 +00:00
copilot-swe-agent[bot]
2bd6d0a9e5 Add Wipe Inventory feature with backend and frontend implementation
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:52:04 +00:00
copilot-swe-agent[bot]
88275620f2 Initial plan for Wipe Inventory feature
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:47:42 +00:00
copilot-swe-agent[bot]
5a058250e6 Initial plan 2025-12-27 14:43:31 +00:00
copilot-swe-agent[bot]
afd7a10003 Fix TypeScript null check in upgrade-verification test
Add null check for pageContent before accessing length property

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:34:59 +00:00
copilot-swe-agent[bot]
8eedd1e39d Fix ESLint errors in upgrade-verification.spec.ts
- Remove unused 'path' import
- Replace 'any' types with proper TypeScript interfaces
- Fix all Prettier formatting issues

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:32:57 +00:00
copilot-swe-agent[bot]
fedeb1a7e5 Add proper GITHUB_TOKEN permissions to workflow
Set minimal required permissions (contents:read, packages:read) to follow security best practices

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:14:52 +00:00
copilot-swe-agent[bot]
69b31a3be5 Improve test reliability and fix security issues
- Replace waitForTimeout with waitForSelector and waitForLoadState
- Remove eval security risk in bash script
- Use proper wait mechanisms for better test reliability

Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:12:22 +00:00
copilot-swe-agent[bot]
31d306ca05 Add comprehensive documentation for upgrade test workflow
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:09:37 +00:00
copilot-swe-agent[bot]
1bfb716cea Add upgrade test workflow with data generation and verification
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:08:13 +00:00
copilot-swe-agent[bot]
13b1524c56 Initial plan for upgrade test workflow
Co-authored-by: katosdev <7927609+katosdev@users.noreply.github.com>
2025-12-27 14:06:05 +00:00
copilot-swe-agent[bot]
b18599b6f4 Initial plan 2025-12-27 14:02:31 +00:00
Matthew Kilgore
473027c1ae Fix notifiers gettings wiped 2025-12-26 17:27:18 -05:00
Matthew Kilgore
3a77440996 Fix flip flopped columns 2025-12-26 15:50:04 -05:00
Matthew Kilgore
731765c36c Make sure the right columns get migrated into the correct columns 2025-12-26 15:09:02 -05:00
158 changed files with 11775 additions and 7342 deletions

10
.github/ISSUE_TEMPLATE/internal.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: "🛠️ Internal / Developer Issue"
about: "Unstructured issue for project members only. Outside contributors: please use a standard template."
title: "[INT]: "
labels: ["internal"]
assignees: []
---
**Summary:**
[Write here]

View File

@@ -0,0 +1,432 @@
---
applyTo: '/backend/app/api/handlers/**/*'
---
# Backend API Handlers Instructions (`/backend/app/api/handlers/v1/`)
## Overview
API handlers are the HTTP layer that processes requests, calls services, and returns responses. All handlers use the V1 API pattern with Swagger documentation for auto-generation.
## Architecture Flow
```
HTTP Request → Router → Middleware → Handler → Service → Repository → Database
HTTP Response
```
## Directory Structure
```
backend/app/api/
├── routes.go # Route definitions and middleware
├── handlers/
│ └── v1/
│ ├── controller.go # V1Controller struct and dependencies
│ ├── v1_ctrl_items.go # Item endpoints
│ ├── v1_ctrl_users.go # User endpoints
│ ├── v1_ctrl_locations.go # Location endpoints
│ ├── v1_ctrl_auth.go # Authentication endpoints
│ ├── helpers.go # HTTP helper functions
│ ├── query_params.go # Query parameter parsing
│ └── assets/ # Asset handling
```
## Handler Structure
### V1Controller
All handlers are methods on `V1Controller`:
```go
type V1Controller struct {
svc *services.AllServices // Service layer
repo *repo.AllRepos // Direct repo access (rare)
bus *eventbus.EventBus // Event publishing
}
func (ctrl *V1Controller) HandleItemCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// Handler logic
}
}
```
### Swagger Documentation
**CRITICAL:** Every handler must have Swagger comments for API doc generation:
```go
// HandleItemsGetAll godoc
//
// @Summary Query All Items
// @Tags Items
// @Produce json
// @Param q query string false "search string"
// @Param page query int false "page number"
// @Param pageSize query int false "items per page"
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// ...
}
}
```
**After modifying Swagger comments, ALWAYS run:**
```bash
task generate # Regenerates Swagger docs and TypeScript types
```
## Standard Handler Pattern
### 1. Decode Request
```go
func (ctrl *V1Controller) HandleItemCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
var itemData repo.ItemCreate
if err := server.Decode(r, &itemData); err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
// ... rest of handler
}
}
```
### 2. Extract Context
```go
// Get current user from request (added by auth middleware)
user := ctrl.CurrentUser(r)
// Create service context with group/user IDs
ctx := services.NewContext(r.Context(), user)
```
### 3. Call Service
```go
result, err := ctrl.svc.Items.Create(ctx, itemData)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
```
### 4. Return Response
```go
return server.JSON(w, result, http.StatusCreated)
```
## Common Handler Patterns
### GET - Single Item
```go
// HandleItemGet godoc
//
// @Summary Get Item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
id, err := ctrl.RouteUUID(r, "id")
if err != nil {
return err
}
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
item, err := ctrl.svc.Items.Get(ctx, id)
if err != nil {
return validate.NewRequestError(err, http.StatusNotFound)
}
return server.JSON(w, item, http.StatusOK)
}
}
```
### GET - List with Pagination
```go
func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// Parse query parameters
query := extractItemQuery(r)
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
items, err := ctrl.svc.Items.GetAll(ctx, query)
if err != nil {
return err
}
return server.JSON(w, items, http.StatusOK)
}
}
// Helper to extract query params
func extractItemQuery(r *http.Request) repo.ItemQuery {
params := r.URL.Query()
return repo.ItemQuery{
Page: queryIntOrNegativeOne(params.Get("page")),
PageSize: queryIntOrNegativeOne(params.Get("pageSize")),
Search: params.Get("q"),
LocationIDs: queryUUIDList(params, "locations"),
}
}
```
### POST - Create
```go
// HandleItemCreate godoc
//
// @Summary Create Item
// @Tags Items
// @Accept json
// @Produce json
// @Param payload body repo.ItemCreate true "Item Data"
// @Success 201 {object} repo.ItemOut
// @Router /v1/items [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
var data repo.ItemCreate
if err := server.Decode(r, &data); err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
item, err := ctrl.svc.Items.Create(ctx, data)
if err != nil {
return err
}
return server.JSON(w, item, http.StatusCreated)
}
}
```
### PUT - Update
```go
// HandleItemUpdate godoc
//
// @Summary Update Item
// @Tags Items
// @Accept json
// @Produce json
// @Param id path string true "Item ID"
// @Param payload body repo.ItemUpdate true "Item Data"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemUpdate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
id, err := ctrl.RouteUUID(r, "id")
if err != nil {
return err
}
var data repo.ItemUpdate
if err := server.Decode(r, &data); err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
item, err := ctrl.svc.Items.Update(ctx, id, data)
if err != nil {
return err
}
return server.JSON(w, item, http.StatusOK)
}
}
```
### DELETE
```go
// HandleItemDelete godoc
//
// @Summary Delete Item
// @Tags Items
// @Param id path string true "Item ID"
// @Success 204
// @Router /v1/items/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
id, err := ctrl.RouteUUID(r, "id")
if err != nil {
return err
}
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
err = ctrl.svc.Items.Delete(ctx, id)
if err != nil {
return err
}
return server.JSON(w, nil, http.StatusNoContent)
}
}
```
### File Upload
```go
func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
id, err := ctrl.RouteUUID(r, "id")
if err != nil {
return err
}
// Parse multipart form
err = r.ParseMultipartForm(32 << 20) // 32MB max
if err != nil {
return err
}
file, header, err := r.FormFile("file")
if err != nil {
return err
}
defer file.Close()
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
attachment, err := ctrl.svc.Items.CreateAttachment(ctx, id, file, header.Filename)
if err != nil {
return err
}
return server.JSON(w, attachment, http.StatusCreated)
}
}
```
## Routing
Routes are defined in `backend/app/api/routes.go`:
```go
func (a *app) mountRoutes(repos *repo.AllRepos, svc *services.AllServices) {
v1 := v1.NewControllerV1(svc, repos)
a.server.Get("/api/v1/items", v1.HandleItemsGetAll())
a.server.Post("/api/v1/items", v1.HandleItemCreate())
a.server.Get("/api/v1/items/{id}", v1.HandleItemGet())
a.server.Put("/api/v1/items/{id}", v1.HandleItemUpdate())
a.server.Delete("/api/v1/items/{id}", v1.HandleItemDelete())
}
```
## Helper Functions
### Query Parameter Parsing
Located in `query_params.go`:
```go
func queryIntOrNegativeOne(s string) int
func queryBool(s string) bool
func queryUUIDList(params url.Values, key string) []uuid.UUID
```
### Response Helpers
```go
// From httpkit/server
server.JSON(w, data, statusCode) // JSON response
server.Respond(w, statusCode) // Empty response
validate.NewRequestError(err, statusCode) // Error response
```
### Authentication
```go
user := ctrl.CurrentUser(r) // Get authenticated user (from middleware)
```
## Adding a New Endpoint
### 1. Create Handler
In `backend/app/api/handlers/v1/v1_ctrl_myentity.go`:
```go
// HandleMyEntityCreate godoc
//
// @Summary Create MyEntity
// @Tags MyEntity
// @Accept json
// @Produce json
// @Param payload body repo.MyEntityCreate true "Data"
// @Success 201 {object} repo.MyEntityOut
// @Router /v1/my-entity [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleMyEntityCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
var data repo.MyEntityCreate
if err := server.Decode(r, &data); err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
result, err := ctrl.svc.MyEntity.Create(ctx, data)
if err != nil {
return err
}
return server.JSON(w, result, http.StatusCreated)
}
}
```
### 2. Add Route
In `backend/app/api/routes.go`:
```go
a.server.Post("/api/v1/my-entity", v1.HandleMyEntityCreate())
```
### 3. Generate Docs
```bash
task generate # Generates Swagger docs and TypeScript types
```
### 4. Test
```bash
task go:build # Verify builds
task go:test # Run tests
```
## Critical Rules
1. **ALWAYS add Swagger comments** - required for API docs and TypeScript type generation
2. **Run `task generate` after handler changes** - updates API documentation
3. **Use services, not repos directly** - handlers call services, services call repos
4. **Always use `services.Context`** - includes auth and multi-tenancy
5. **Handle errors properly** - use `validate.NewRequestError()` with appropriate status codes
6. **Validate input** - decode and validate request bodies
7. **Return correct status codes** - 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found
## Common Issues
- **"Missing Swagger docs"** → Add `@Summary`, `@Tags`, `@Router` comments, run `task generate`
- **TypeScript types outdated** → Run `task generate` to regenerate
- **Auth failures** → Ensure route has auth middleware and `@Security Bearer`
- **CORS errors** → Check middleware configuration in `routes.go`

View File

@@ -0,0 +1,341 @@
---
applyTo: '/backend/internal/core/services/**/*'
---
# Backend Services Layer Instructions (`/backend/internal/core/services/`)
## Overview
The services layer contains business logic that orchestrates between repositories and API handlers. Services handle complex operations, validation, and cross-cutting concerns.
## Architecture Pattern
```
Handler (API) → Service (Business Logic) → Repository (Data Access) → Database
```
**Separation of concerns:**
- **Handlers** (`backend/app/api/handlers/v1/`) - HTTP request/response, routing, auth
- **Services** (`backend/internal/core/services/`) - Business logic, orchestration
- **Repositories** (`backend/internal/data/repo/`) - Database operations, queries
## Directory Structure
```
backend/internal/core/services/
├── all.go # Service aggregation
├── service_items.go # Item business logic
├── service_items_attachments.go # Item attachments logic
├── service_user.go # User management logic
├── service_group.go # Group management logic
├── service_background.go # Background tasks
├── contexts.go # Service context types
├── reporting/ # Reporting subsystem
│ ├── eventbus/ # Event bus for notifications
│ └── *.go # Report generation logic
└── *_test.go # Service tests
```
## Service Structure
### Standard Pattern
```go
type ItemService struct {
repo *repo.AllRepos // Access to all repositories
filepath string // File storage path
autoIncrementAssetID bool // Feature flags
}
func (svc *ItemService) Create(ctx Context, item repo.ItemCreate) (repo.ItemOut, error) {
// 1. Validation
if item.Name == "" {
return repo.ItemOut{}, errors.New("name required")
}
// 2. Business logic
if svc.autoIncrementAssetID {
highest, err := svc.repo.Items.GetHighestAssetID(ctx, ctx.GID)
if err != nil {
return repo.ItemOut{}, err
}
item.AssetID = highest + 1
}
// 3. Repository call
return svc.repo.Items.Create(ctx, ctx.GID, item)
}
```
### Service Context
Services use a custom `Context` type that extends `context.Context`:
```go
type Context struct {
context.Context
GID uuid.UUID // Group ID for multi-tenancy
UID uuid.UUID // User ID for audit
}
```
**Always use `Context` from services package, not raw `context.Context`.**
## Common Service Patterns
### 1. CRUD with Business Logic
```go
func (svc *ItemService) Update(ctx Context, id uuid.UUID, data repo.ItemUpdate) (repo.ItemOut, error) {
// Fetch existing
existing, err := svc.repo.Items.Get(ctx, id)
if err != nil {
return repo.ItemOut{}, err
}
// Business rules
if existing.Archived && data.Quantity != nil {
return repo.ItemOut{}, errors.New("cannot modify archived items")
}
// Update
return svc.repo.Items.Update(ctx, id, data)
}
```
### 2. Orchestrating Multiple Repositories
```go
func (svc *ItemService) CreateWithAttachment(ctx Context, item repo.ItemCreate, file io.Reader) (repo.ItemOut, error) {
// Create item
created, err := svc.repo.Items.Create(ctx, ctx.GID, item)
if err != nil {
return repo.ItemOut{}, err
}
// Upload attachment
attachment, err := svc.repo.Attachments.Create(ctx, created.ID, file)
if err != nil {
// Rollback - delete item
_ = svc.repo.Items.Delete(ctx, created.ID)
return repo.ItemOut{}, err
}
created.Attachments = []repo.AttachmentOut{attachment}
return created, nil
}
```
### 3. Background Tasks
```go
func (svc *ItemService) EnsureAssetID(ctx context.Context, gid uuid.UUID) (int, error) {
// Get items without asset IDs
items, err := svc.repo.Items.GetAllZeroAssetID(ctx, gid)
if err != nil {
return 0, err
}
// Batch assign
highest := svc.repo.Items.GetHighestAssetID(ctx, gid)
for _, item := range items {
highest++
_ = svc.repo.Items.Update(ctx, item.ID, repo.ItemUpdate{
AssetID: &highest,
})
}
return len(items), nil
}
```
### 4. Event Publishing
Services can publish events to the event bus:
```go
func (svc *ItemService) Delete(ctx Context, id uuid.UUID) error {
err := svc.repo.Items.Delete(ctx, id)
if err != nil {
return err
}
// Publish event for notifications
svc.repo.Bus.Publish(eventbus.Event{
Type: "item.deleted",
Data: map[string]interface{}{"id": id},
})
return nil
}
```
## Service Aggregation
All services are bundled in `all.go`:
```go
type AllServices struct {
User *UserService
Group *GroupService
Items *ItemService
// ... other services
}
func New(repos *repo.AllRepos, filepath string) *AllServices {
return &AllServices{
User: &UserService{repo: repos},
Items: &ItemService{repo: repos, filepath: filepath},
// ...
}
}
```
**Accessed in handlers via:**
```go
ctrl.svc.Items.Create(ctx, itemData)
```
## Working with Services from Handlers
Handlers call services, not repositories directly:
```go
// In backend/app/api/handlers/v1/v1_ctrl_items.go
func (ctrl *V1Controller) HandleItemCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
var itemData repo.ItemCreate
if err := server.Decode(r, &itemData); err != nil {
return err
}
// Get context with group/user IDs
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
// Call service (not repository)
item, err := ctrl.svc.Items.Create(ctx, itemData)
if err != nil {
return err
}
return server.JSON(w, item, http.StatusCreated)
}
}
```
## Testing Services
Service tests mock repositories using interfaces:
```go
func TestItemService_Create(t *testing.T) {
mockRepo := &mockItemRepo{
CreateFunc: func(ctx context.Context, gid uuid.UUID, data repo.ItemCreate) (repo.ItemOut, error) {
return repo.ItemOut{ID: uuid.New(), Name: data.Name}, nil
},
}
svc := &ItemService{repo: &repo.AllRepos{Items: mockRepo}}
ctx := services.Context{GID: uuid.New(), UID: uuid.New()}
result, err := svc.Create(ctx, repo.ItemCreate{Name: "Test"})
assert.NoError(t, err)
assert.Equal(t, "Test", result.Name)
}
```
**Run service tests:**
```bash
cd backend && go test ./internal/core/services -v
```
## Adding a New Service
### 1. Create Service File
Create `backend/internal/core/services/service_myentity.go`:
```go
package services
type MyEntityService struct {
repo *repo.AllRepos
}
func (svc *MyEntityService) Create(ctx Context, data repo.MyEntityCreate) (repo.MyEntityOut, error) {
// Business logic here
return svc.repo.MyEntity.Create(ctx, ctx.GID, data)
}
```
### 2. Add to AllServices
Edit `backend/internal/core/services/all.go`:
```go
type AllServices struct {
// ... existing services
MyEntity *MyEntityService
}
func New(repos *repo.AllRepos, filepath string) *AllServices {
return &AllServices{
// ... existing services
MyEntity: &MyEntityService{repo: repos},
}
}
```
### 3. Use in Handler
In `backend/app/api/handlers/v1/`:
```go
func (ctrl *V1Controller) HandleMyEntityCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context(), ctrl.CurrentUser(r))
result, err := ctrl.svc.MyEntity.Create(ctx, data)
// ...
}
}
```
### 4. Run Tests
```bash
task generate # If you modified schemas
task go:test # Run all tests
```
## Common Service Responsibilities
**Services should:**
- ✅ Contain business logic and validation
- ✅ Orchestrate multiple repository calls
- ✅ Handle transactions (when needed)
- ✅ Publish events for side effects
- ✅ Enforce access control and multi-tenancy
- ✅ Transform data between API and repository formats
**Services should NOT:**
- ❌ Handle HTTP requests/responses (that's handlers)
- ❌ Construct SQL queries (that's repositories)
- ❌ Import handler packages (creates circular deps)
- ❌ Directly access database (use repositories)
## Critical Rules
1. **Always use `services.Context`** - includes group/user IDs for multi-tenancy
2. **Services call repos, handlers call services** - maintains layer separation
3. **No direct database access** - always through repositories
4. **Business logic goes here** - not in handlers or repositories
5. **Test services independently** - mock repository dependencies
## Common Patterns to Follow
- **Validation:** Check business rules before calling repository
- **Error wrapping:** Add context to repository errors
- **Logging:** Use `log.Ctx(ctx)` for contextual logging
- **Transactions:** Use `repo.WithTx()` for multi-step operations
- **Events:** Publish to event bus for notifications/side effects

View File

@@ -0,0 +1,239 @@
---
applyTo: 'backend/internal/data/**/*'
---
# Backend Data Layer Instructions (`/backend/internal/data/`)
## Overview
This directory contains the data access layer using **Ent ORM** (entity framework). It follows a clear separation between schema definitions, generated code, and repository implementations.
## Directory Structure
```
backend/internal/data/
├── ent/ # Ent ORM generated code (DO NOT EDIT)
│ ├── schema/ # Schema definitions (EDIT THESE)
│ │ ├── item.go # Item entity schema
│ │ ├── user.go # User entity schema
│ │ ├── location.go # Location entity schema
│ │ ├── label.go # Label entity schema
│ │ └── mixins/ # Reusable schema mixins
│ ├── *.go # Generated entity code
│ └── migrate/ # Generated migrations
├── repo/ # Repository pattern implementations
│ ├── repos_all.go # Aggregates all repositories
│ ├── repo_items.go # Item repository
│ ├── repo_users.go # User repository
│ ├── repo_locations.go # Location repository
│ └── *_test.go # Repository tests
├── migrations/ # Manual SQL migrations
│ ├── sqlite3/ # SQLite-specific migrations
│ └── postgres/ # PostgreSQL-specific migrations
└── types/ # Custom data types
```
## Ent ORM Workflow
### 1. Defining Schemas (`ent/schema/`)
**ALWAYS edit schema files here** - these define your database entities:
```go
// Example: backend/internal/data/ent/schema/item.go
type Item struct {
ent.Schema
}
func (Item) Fields() []ent.Field {
return []ent.Field{
field.String("name").NotEmpty(),
field.Int("quantity").Default(1),
field.Bool("archived").Default(false),
}
}
func (Item) Edges() []ent.Edge {
return []ent.Edge{
edge.From("location", Location.Type).Ref("items").Unique(),
edge.From("labels", Label.Type).Ref("items"),
}
}
func (Item) Indexes() []ent.Index {
return []ent.Index{
index.Fields("name"),
index.Fields("archived"),
}
}
```
**Common schema patterns:**
- Use `mixins.BaseMixin{}` for `id`, `created_at`, `updated_at` fields
- Use `mixins.DetailsMixin{}` for `name` and `description` fields
- Use `GroupMixin{ref: "items"}` to link entities to groups
- Add indexes for frequently queried fields
### 2. Generating Code
**After modifying any schema file, ALWAYS run:**
```bash
task generate
```
This:
1. Runs `go generate ./...` in `backend/internal/` (generates Ent code)
2. Generates Swagger docs from API handlers
3. Generates TypeScript types for frontend
**Generated files you'll see:**
- `ent/*.go` - Entity types, builders, queries
- `ent/migrate/migrate.go` - Auto migrations
- `ent/predicate/predicate.go` - Query predicates
**NEVER edit generated files directly** - changes will be overwritten.
### 3. Using Generated Code in Repositories
Repositories in `repo/` use the generated Ent client:
```go
// Example: backend/internal/data/repo/repo_items.go
type ItemsRepository struct {
db *ent.Client
bus *eventbus.EventBus
}
func (r *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCreate) (ItemOut, error) {
entity, err := r.db.Item.Create().
SetName(data.Name).
SetQuantity(data.Quantity).
SetGroupID(gid).
Save(ctx)
return mapToItemOut(entity), err
}
```
## Repository Pattern
### Structure
Each entity typically has:
- **Repository struct** (`ItemsRepository`) - holds DB client and dependencies
- **Input types** (`ItemCreate`, `ItemUpdate`) - API input DTOs
- **Output types** (`ItemOut`, `ItemSummary`) - API response DTOs
- **Query types** (`ItemQuery`) - search/filter parameters
- **Mapper functions** (`mapToItemOut`) - converts Ent entities to output DTOs
### Key Methods
Repositories typically implement:
- `Create(ctx, gid, input)` - Create new entity
- `Get(ctx, id)` - Get single entity by ID
- `GetAll(ctx, gid, query)` - Query with pagination/filters
- `Update(ctx, id, input)` - Update entity
- `Delete(ctx, id)` - Delete entity
### Working with Ent Queries
**Loading relationships (edges):**
```go
items, err := r.db.Item.Query().
WithLocation(). // Load location edge
WithLabels(). // Load labels edge
WithChildren(). // Load child items
Where(item.GroupIDEQ(gid)).
All(ctx)
```
**Filtering:**
```go
query := r.db.Item.Query().
Where(
item.GroupIDEQ(gid),
item.ArchivedEQ(false),
item.NameContainsFold(search),
)
```
**Ordering and pagination:**
```go
items, err := query.
Order(ent.Desc(item.FieldCreatedAt)).
Limit(pageSize).
Offset((page - 1) * pageSize).
All(ctx)
```
## Common Workflows
### Adding a New Entity
1. **Create schema:** `backend/internal/data/ent/schema/myentity.go`
2. **Run:** `task generate` (generates Ent code)
3. **Create repository:** `backend/internal/data/repo/repo_myentity.go`
4. **Add to AllRepos:** Edit `repo/repos_all.go` to include new repo
5. **Run tests:** `task go:test`
### Adding Fields to Existing Entity
1. **Edit schema:** `backend/internal/data/ent/schema/item.go`
```go
field.String("new_field").Optional()
```
2. **Run:** `task generate`
3. **Update repository:** Add field to input/output types in `repo/repo_items.go`
4. **Update mappers:** Ensure mapper functions handle new field
5. **Run tests:** `task go:test`
### Adding Relationships (Edges)
1. **Edit both schemas:**
```go
// In item.go
edge.From("location", Location.Type).Ref("items").Unique()
// In location.go
edge.To("items", Item.Type)
```
2. **Run:** `task generate`
3. **Use in queries:** `.WithLocation()` to load the edge
4. **Run tests:** `task go:test`
## Testing
Repository tests use `enttest` for in-memory SQLite:
```go
func TestItemRepo(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&_fk=1")
defer client.Close()
repo := &ItemsRepository{db: client}
// Test methods...
}
```
**Run repository tests:**
```bash
cd backend && go test ./internal/data/repo -v
```
## Critical Rules
1. **ALWAYS run `task generate` after schema changes** - builds will fail otherwise
2. **NEVER edit files in `ent/` except `ent/schema/`** - they're generated
3. **Use repositories, not raw Ent queries in services/handlers** - maintains separation
4. **Include `group_id` in all queries** - ensures multi-tenancy
5. **Use `.WithX()` to load edges** - avoids N+1 queries
6. **Test with both SQLite and PostgreSQL** - CI tests both
## Common Errors
- **"undefined: ent.ItemX"** → Run `task generate` after schema changes
- **Migration conflicts** → Check `migrations/` for manual migration files
- **Foreign key violations** → Ensure edges are properly defined in both schemas
- **Slow queries** → Add indexes in schema `Indexes()` method

View File

@@ -0,0 +1,157 @@
# Homebox Repository Instructions for Coding Agents
## Repository Overview
**Type**: Full-stack home inventory management web app (monorepo)
**Size**: ~265 Go files, ~371 TypeScript/Vue files
**Build Tool**: Task (Taskfile.yml) - **ALWAYS use `task` commands**
**Database**: SQLite (default) or PostgreSQL
### Stack
- **Backend** (`/backend`): Go 1.24+, Chi router, Ent ORM, port 7745
- **Frontend** (`/frontend`): Nuxt 4, Vue 3, TypeScript, Tailwind CSS, pnpm 9.1.4+, dev proxies to backend
## Critical Build & Validation Commands
### Initial Setup (Run Once)
```bash
task setup # Installs swag, goose, Go deps, pnpm deps
```
### Code Generation (Required Before Backend Work)
```bash
task generate # Generates Ent ORM, Swagger docs, TypeScript types
```
**ALWAYS run after**: schema changes, API handler changes, before backend server/tests
**Note**: "TypeSpecDef is nil" warnings are normal - ignore them
### Backend Commands
```bash
task go:build # Build binary (60-90s)
task go:test # Unit tests (5-10s)
task go:lint # golangci-lint (6m timeout in CI)
task go:all # Tidy + lint + test
task go:run # Start server (SQLite)
task pr # Full PR validation (3-5 min)
```
### Frontend Commands
```bash
task ui:dev # Dev server port 3000
task ui:check # Type checking
task ui:fix # eslint --fix + prettier
task ui:watch # Vitest watch mode
```
**Lint**: Max 1 warning in CI (`pnpm run lint:ci`)
### Testing
```bash
task test:ci # Integration tests (15-30s + startup)
task test:e2e # Playwright E2E (60s+ per shard, needs playwright install)
task pr # Full PR validation: generate + go:all + ui:check + ui:fix + test:ci (3-5 min)
```
## Project Structure
### Key Root Files
- `Taskfile.yml` - All commands (always use `task`)
- `docker-compose.yml`, `Dockerfile*` - Docker configs
- `CONTRIBUTING.md` - Contribution guidelines
### Backend Structure (`/backend`)
```
backend/
├── app/
│ ├── api/ # Main API application
│ │ ├── main.go # Entry point
│ │ ├── routes.go # Route definitions
│ │ ├── handlers/ # HTTP handlers (v1 API)
│ │ ├── static/ # Swagger docs, embedded frontend
│ │ └── providers/ # Service providers
│ └── tools/
│ └── typegen/ # TypeScript type generation tool
├── internal/
│ ├── core/
│ │ └── services/ # Business logic layer
│ ├── data/
│ │ ├── ent/ # Ent ORM generated code + schemas
│ │ │ └── schema/ # Schema definitions (edit these)
│ │ └── repo/ # Repository pattern implementations
│ ├── sys/ # System utilities (config, validation)
│ └── web/ # Web middleware
├── pkgs/ # Reusable packages
├── go.mod, go.sum # Go dependencies
└── .golangci.yml # Linter configuration
```
**Patterns**: Schema/API changes → edit source → `task generate`. Never edit generated code in `ent/`.
### Frontend Structure (`/frontend`)
```
frontend/
├── app.vue # Root component
├── nuxt.config.ts # Nuxt configuration
├── package.json # Frontend dependencies
├── components/ # Vue components (auto-imported)
├── pages/ # File-based routing
├── layouts/ # Layout components
├── composables/ # Vue composables (auto-imported)
├── stores/ # Pinia state stores
├── lib/
│ └── api/
│ └── types/ # Generated TypeScript API types
├── locales/ # i18n translations
├── test/ # Vitest + Playwright tests
├── eslint.config.mjs # ESLint configuration
└── tailwind.config.js # Tailwind configuration
```
**Patterns**: Auto-imports for `components/` and `composables/`. API types auto-generated - never edit manually.
## CI/CD Workflows
PR checks (`.github/workflows/pull-requests.yaml`) on `main`/`vnext`:
1. **Backend**: Go 1.24, golangci-lint, `task go:build`, `task go:coverage`
2. **Frontend**: Lint (max 1 warning), typecheck, `task test:ci` (SQLite + PostgreSQL v15-17)
3. **E2E**: 4 sharded Playwright runs (60min timeout)
All must pass before merge.
## Common Pitfalls
1. **Missing tools**: Run `task setup` first (installs swag, goose, deps)
2. **Stale generated code**: Always `task generate` after schema/API changes
3. **Test failures**: Integration tests may fail first run (race condition) - retry
4. **Port in use**: Backend uses 7745 - kill existing process
5. **SQLite locked**: Delete `.data/homebox.db-*` files
6. **Clean build**: `rm -rf build/ backend/app/api/static/public/ frontend/.nuxt`
## Environment Variables
Backend defaults in `Taskfile.yml`:
- `HBOX_LOG_LEVEL=debug`
- `HBOX_DATABASE_DRIVER=sqlite3` (or `postgres`)
- `HBOX_DATABASE_SQLITE_PATH=.data/homebox.db?_pragma=busy_timeout=1000&_pragma=journal_mode=WAL&_fk=1`
- PostgreSQL: `HBOX_DATABASE_*` vars for username/password/host/port/database
## Validation Checklist
Before PR:
- [ ] `task generate` after schema/API changes
- [ ] `task pr` passes (includes lint, test, typecheck)
- [ ] No build artifacts committed (check `.gitignore`)
- [ ] Code matches existing patterns
## Quick Reference
**Dev environment**: `task go:run` (terminal 1) + `task ui:dev` (terminal 2)
**API changes**: Edit handlers → add Swagger comments → `task generate``task go:build``task go:test`
**Schema changes**: Edit `ent/schema/*.go``task generate` → update repo methods → `task go:test`
**Specific tests**: `cd backend && go test ./path -v` or `cd frontend && pnpm run test:watch`
## Trust These Instructions
Instructions are validated and current. Only explore further if info is incomplete, incorrect, or you encounter undocumented errors. Use `task --list-all` for all commands.

View File

@@ -0,0 +1,480 @@
---
applyTo: 'frontend/**/*'
---
# Frontend Components & Pages Instructions (`/frontend/`)
## Overview
The frontend is a Nuxt 4 application with Vue 3 and TypeScript. It uses auto-imports for components and composables, file-based routing, and generated TypeScript types from the backend API.
## Directory Structure
```
frontend/
├── components/ # Vue components (auto-imported)
│ ├── Item/ # Item-related components
│ ├── Location/ # Location components
│ ├── Label/ # Label components
│ ├── Form/ # Form components
│ └── ui/ # Shadcn-vue UI components
├── pages/ # File-based routes (auto-routing)
│ ├── index.vue # Home page (/)
│ ├── items.vue # Items list (/items)
│ ├── item/
│ │ └── [id].vue # Item detail (/item/:id)
│ ├── locations.vue # Locations list (/locations)
│ └── profile.vue # User profile (/profile)
├── composables/ # Vue composables (auto-imported)
│ ├── use-api.ts # API client wrapper
│ ├── use-auth.ts # Authentication
│ └── use-user-api.ts # User API helpers
├── stores/ # Pinia state management
│ ├── auth.ts # Auth state
│ └── preferences.ts # User preferences
├── lib/
│ └── api/
│ └── types/ # Generated TypeScript types (DO NOT EDIT)
├── layouts/ # Layout components
│ └── default.vue # Default layout
├── locales/ # i18n translations
├── test/ # Tests (Vitest + Playwright)
└── nuxt.config.ts # Nuxt configuration
```
## Auto-Imports
### Components
Components in `components/` are **automatically imported** - no import statement needed:
```vue
<!-- components/Item/Card.vue -->
<template>
<div class="item-card">{{ item.name }}</div>
</template>
<!-- pages/items.vue - NO import needed -->
<template>
<ItemCard :item="item" />
</template>
```
**Naming convention:** Nested path becomes component name
- `components/Item/Card.vue``<ItemCard />`
- `components/Form/TextField.vue``<FormTextField />`
### Composables
Composables in `composables/` are **automatically imported**:
```ts
// composables/use-items.ts
export function useItems() {
const api = useUserApi()
async function getItems() {
const { data } = await api.items.getAll()
return data
}
return { getItems }
}
// pages/items.vue - NO import needed
const { getItems } = useItems()
const items = await getItems()
```
## File-Based Routing
Pages in `pages/` automatically become routes:
```
pages/index.vue → /
pages/items.vue → /items
pages/item/[id].vue → /item/:id
pages/locations.vue → /locations
pages/location/[id].vue → /location/:id
pages/profile.vue → /profile
```
### Dynamic Routes
Use square brackets for dynamic segments:
```vue
<!-- pages/item/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const id = route.params.id
const { data: item } = await useUserApi().items.getOne(id)
</script>
<template>
<div>
<h1>{{ item.name }}</h1>
</div>
</template>
```
## API Integration
### Generated Types
API types are auto-generated from backend Swagger docs:
```ts
// lib/api/types/data-contracts.ts (GENERATED - DO NOT EDIT)
export interface ItemOut {
id: string
name: string
quantity: number
createdAt: Date | string
updatedAt: Date | string
}
export interface ItemCreate {
name: string
quantity?: number
locationId?: string
}
```
**Regenerate after backend API changes:**
```bash
task generate # Runs in backend, updates frontend/lib/api/types/
```
### Using the API Client
The `useUserApi()` composable provides typed API access:
```vue
<script setup lang="ts">
import type { ItemCreate, ItemOut } from '~/lib/api/types/data-contracts'
const api = useUserApi()
// GET all items
const { data: items } = await api.items.getAll({
q: 'search term',
page: 1,
pageSize: 20
})
// GET single item
const { data: item } = await api.items.getOne(itemId)
// POST create item
const newItem: ItemCreate = {
name: 'New Item',
quantity: 1
}
const { data: created } = await api.items.create(newItem)
// PUT update item
const { data: updated } = await api.items.update(itemId, {
quantity: 5
})
// DELETE item
await api.items.delete(itemId)
</script>
```
## Component Patterns
### Standard Vue 3 Composition API
```vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { ItemOut } from '~/lib/api/types/data-contracts'
// Props
interface Props {
item: ItemOut
editable?: boolean
}
const props = defineProps<Props>()
// Emits
interface Emits {
(e: 'update', item: ItemOut): void
(e: 'delete', id: string): void
}
const emit = defineEmits<Emits>()
// State
const isEditing = ref(false)
const localItem = ref({ ...props.item })
// Computed
const displayName = computed(() => {
return props.item.name.toUpperCase()
})
// Methods
function handleSave() {
emit('update', localItem.value)
isEditing.value = false
}
</script>
<template>
<div class="item-card">
<h3>{{ displayName }}</h3>
<p v-if="!isEditing">Quantity: {{ item.quantity }}</p>
<input
v-if="isEditing"
v-model.number="localItem.quantity"
type="number"
/>
<button v-if="editable" @click="isEditing = !isEditing">
{{ isEditing ? 'Cancel' : 'Edit' }}
</button>
<button v-if="isEditing" @click="handleSave">Save</button>
</div>
</template>
<style scoped>
.item-card {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 0.5rem;
}
</style>
```
### Using Pinia Stores
```vue
<script setup lang="ts">
import { useAuthStore } from '~/stores/auth'
const authStore = useAuthStore()
// Access state
const user = computed(() => authStore.user)
const isLoggedIn = computed(() => authStore.isLoggedIn)
// Call actions
async function logout() {
await authStore.logout()
navigateTo('/login')
}
</script>
```
### Form Handling
```vue
<script setup lang="ts">
import { useForm } from 'vee-validate'
import type { ItemCreate } from '~/lib/api/types/data-contracts'
const api = useUserApi()
const { values, errors, handleSubmit } = useForm<ItemCreate>({
initialValues: {
name: '',
quantity: 1
}
})
const onSubmit = handleSubmit(async (values) => {
try {
const { data } = await api.items.create(values)
navigateTo(`/item/${data.id}`)
} catch (error) {
console.error('Failed to create item:', error)
}
})
</script>
<template>
<form @submit.prevent="onSubmit">
<input v-model="values.name" type="text" placeholder="Item name" />
<span v-if="errors.name">{{ errors.name }}</span>
<input v-model.number="values.quantity" type="number" />
<span v-if="errors.quantity">{{ errors.quantity }}</span>
<button type="submit">Create Item</button>
</form>
</template>
```
## Styling
### Tailwind CSS
The project uses Tailwind CSS for styling:
```vue
<template>
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-md">
<h3 class="text-lg font-semibold text-gray-900">{{ item.name }}</h3>
<span class="text-sm text-gray-500">Qty: {{ item.quantity }}</span>
</div>
</template>
```
### Shadcn-vue Components
UI components from `components/ui/` (Shadcn-vue):
```vue
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader } from '@/components/ui/card'
</script>
<template>
<Card>
<CardHeader>
<h3>{{ item.name }}</h3>
</CardHeader>
<CardContent>
<p>{{ item.description }}</p>
<Button @click="handleEdit">Edit</Button>
</CardContent>
</Card>
</template>
```
## Testing
### Vitest (Unit/Integration)
Tests use Vitest with the backend API running:
```ts
// test/items.test.ts
import { describe, it, expect } from 'vitest'
import { useUserApi } from '~/composables/use-user-api'
describe('Items API', () => {
it('should create and fetch item', async () => {
const api = useUserApi()
// Create item
const { data: created } = await api.items.create({
name: 'Test Item',
quantity: 1
})
expect(created.name).toBe('Test Item')
// Fetch item
const { data: fetched } = await api.items.getOne(created.id)
expect(fetched.id).toBe(created.id)
})
})
```
**Run tests:**
```bash
task ui:watch # Watch mode
cd frontend && pnpm run test:ci # CI mode
```
### Playwright (E2E)
E2E tests in `test/`:
```ts
// test/e2e/items.spec.ts
import { test, expect } from '@playwright/test'
test('should create new item', async ({ page }) => {
await page.goto('/items')
await page.click('button:has-text("New Item")')
await page.fill('input[name="name"]', 'Test Item')
await page.fill('input[name="quantity"]', '5')
await page.click('button:has-text("Save")')
await expect(page.locator('text=Test Item')).toBeVisible()
})
```
**Run E2E tests:**
```bash
task test:e2e # Full E2E suite
```
## Adding a New Feature
### 1. Update Backend API
Make backend changes first (schema, service, handler):
```bash
# Edit backend files
task generate # Regenerates TypeScript types
```
### 2. Create Component
Create `components/MyFeature/Card.vue`:
```vue
<script setup lang="ts">
import type { MyFeatureOut } from '~/lib/api/types/data-contracts'
interface Props {
feature: MyFeatureOut
}
defineProps<Props>()
</script>
<template>
<div>{{ feature.name }}</div>
</template>
```
### 3. Create Page
Create `pages/my-feature/[id].vue`:
```vue
<script setup lang="ts">
const route = useRoute()
const api = useUserApi()
const { data: feature } = await api.myFeature.getOne(route.params.id)
</script>
<template>
<MyFeatureCard :feature="feature" />
</template>
```
### 4. Test
```bash
task ui:check # Type checking
task ui:fix # Linting
task ui:watch # Run tests
```
## Critical Rules
1. **Never edit generated types** - `lib/api/types/` is auto-generated, run `task generate` after backend changes
2. **No manual imports for components/composables** - auto-imported from `components/` and `composables/`
3. **Use TypeScript** - all `.vue` files use `<script setup lang="ts">`
4. **Follow file-based routing** - pages in `pages/` become routes automatically
5. **Use `useUserApi()` for API calls** - provides typed, authenticated API client
6. **Max 1 linting warning in CI** - run `task ui:fix` before committing
7. **Test with backend running** - integration tests need API server
## Common Issues
- **"Type not found"** → Run `task generate` to regenerate types from backend
- **Component not found** → Check naming (nested path = component name)
- **API call fails** → Ensure backend is running (`task go:run`)
- **Lint errors** → Run `task ui:fix` to auto-fix
- **Type errors** → Run `task ui:check` for detailed errors

259
.github/scripts/upgrade-test/README.md vendored Normal file
View File

@@ -0,0 +1,259 @@
# HomeBox Upgrade Testing Workflow
This document describes the automated upgrade testing workflow for HomeBox.
## Overview
The upgrade test workflow is designed to ensure data integrity and functionality when upgrading HomeBox from one version to another. It automatically:
1. Deploys a stable version of HomeBox
2. Creates test data (users, items, locations, tags, notifiers, attachments)
3. Upgrades to the latest version from the main branch
4. Verifies all data and functionality remain intact
## Workflow File
**Location**: `.github/workflows/upgrade-test.yaml`
## Trigger Conditions
The workflow runs:
- **Daily**: Automatically at 2 AM UTC (via cron schedule)
- **Manual**: Can be triggered manually via GitHub Actions UI
- **On Push**: When changes are made to the workflow files or test scripts
## Test Scenarios
### 1. Environment Setup
- Pulls the latest stable HomeBox Docker image from GHCR
- Starts the application with test configuration
- Ensures the service is healthy and ready
### 2. Data Creation
The workflow creates comprehensive test data using the `create-test-data.sh` script:
#### Users and Groups
- **Group 1**: 5 users (user1@homebox.test through user5@homebox.test)
- **Group 2**: 2 users (user6@homebox.test and user7@homebox.test)
- All users have password: `TestPassword123!`
#### Locations
- **Group 1**: Living Room, Garage
- **Group 2**: Home Office
#### Tags
- **Group 1**: Electronics, Important
- **Group 2**: Work Equipment
#### Items
- **Group 1**: 5 items (Laptop Computer, Power Drill, TV Remote, Tool Box, Coffee Maker)
- **Group 2**: 2 items (Monitor, Keyboard)
#### Attachments
- Multiple attachments added to various items (receipts, manuals, warranties)
#### Notifiers
- **Group 1**: Test notifier named "TESTING"
### 3. Upgrade Process
1. Stops the stable version container
2. Builds a fresh image from the current main branch
3. Copies the database to a new location
4. Starts the new version with the existing data
### 4. Verification Tests
The Playwright test suite (`upgrade-verification.spec.ts`) verifies:
-**User Authentication**: All 7 users can log in with their credentials
-**Data Persistence**: All items, locations, and tags are present
-**Attachments**: File attachments are correctly associated with items
-**Notifiers**: The "TESTING" notifier is still configured
-**UI Functionality**: Version display, theme switching work correctly
-**Data Isolation**: Groups can only see their own data
## Test Data File
The setup script generates a JSON file at `/tmp/test-users.json` containing:
```json
{
"users": [
{
"email": "user1@homebox.test",
"password": "TestPassword123!",
"token": "...",
"group": "1"
},
...
],
"locations": {
"group1": ["location-id-1", "location-id-2"],
"group2": ["location-id-3"]
},
"tags": {...},
"items": {...},
"notifiers": {...}
}
```
This file is used by the Playwright tests to verify data integrity.
## Scripts
### create-test-data.sh
**Location**: `.github/scripts/upgrade-test/create-test-data.sh`
**Purpose**: Creates all test data via the HomeBox REST API
**Environment Variables**:
- `HOMEBOX_URL`: Base URL of the HomeBox instance (default: http://localhost:7745)
- `TEST_DATA_FILE`: Path to output JSON file (default: /tmp/test-users.json)
**Requirements**:
- `curl`: For API calls
- `jq`: For JSON processing
**Usage**:
```bash
export HOMEBOX_URL=http://localhost:7745
./.github/scripts/upgrade-test/create-test-data.sh
```
## Running Tests Locally
To run the upgrade tests locally:
### Prerequisites
```bash
# Install dependencies
sudo apt-get install -y jq curl docker.io
# Install pnpm and Playwright
cd frontend
pnpm install
pnpm exec playwright install --with-deps chromium
```
### Run the test
```bash
# Start stable version
docker run -d \
--name homebox-test \
-p 7745:7745 \
-e HBOX_OPTIONS_ALLOW_REGISTRATION=true \
-v /tmp/homebox-data:/data \
ghcr.io/sysadminsmedia/homebox:latest
# Wait for startup
sleep 10
# Create test data
export HOMEBOX_URL=http://localhost:7745
./.github/scripts/upgrade-test/create-test-data.sh
# Stop container
docker stop homebox-test
docker rm homebox-test
# Build new version
docker build -t homebox:test .
# Start new version with existing data
docker run -d \
--name homebox-test \
-p 7745:7745 \
-e HBOX_OPTIONS_ALLOW_REGISTRATION=true \
-v /tmp/homebox-data:/data \
homebox:test
# Wait for startup
sleep 10
# Run verification tests
cd frontend
TEST_DATA_FILE=/tmp/test-users.json \
E2E_BASE_URL=http://localhost:7745 \
pnpm exec playwright test \
--project=chromium \
test/upgrade/upgrade-verification.spec.ts
# Cleanup
docker stop homebox-test
docker rm homebox-test
```
## Artifacts
The workflow produces several artifacts:
1. **playwright-report-upgrade-test**: HTML report of test results
2. **playwright-traces**: Detailed traces for debugging failures
3. **Docker logs**: Collected on failure for troubleshooting
## Failure Scenarios
The workflow will fail if:
- The stable version fails to start
- Test data creation fails
- The new version fails to start with existing data
- Any verification test fails
- Database migrations fail
## Troubleshooting
### Test Data Creation Fails
Check the Docker logs:
```bash
docker logs homebox-old
```
Verify the API is accessible:
```bash
curl http://localhost:7745/api/v1/status
```
### Verification Tests Fail
1. Download the Playwright report from GitHub Actions artifacts
2. Review the HTML report for detailed failure information
3. Check traces for visual debugging
### Database Issues
If migrations fail:
```bash
# Check database file
ls -lh /tmp/homebox-data-new/homebox.db
# Check Docker logs for migration errors
docker logs homebox-new
```
## Future Enhancements
Potential improvements:
- [ ] Test multiple upgrade paths (e.g., v0.10 → v0.11 → v0.12)
- [ ] Test with PostgreSQL backend in addition to SQLite
- [ ] Add performance benchmarks
- [ ] Test with larger datasets
- [ ] Add API-level verification in addition to UI tests
- [ ] Test backup and restore functionality
## Related Files
- `.github/workflows/upgrade-test.yaml` - Main workflow definition
- `.github/scripts/upgrade-test/create-test-data.sh` - Data generation script
- `frontend/test/upgrade/upgrade-verification.spec.ts` - Playwright verification tests
- `.github/workflows/e2e-partial.yaml` - Standard E2E test workflow (for reference)
## Support
For issues or questions about this workflow:
1. Check the GitHub Actions run logs
2. Review this documentation
3. Open an issue in the repository

View File

@@ -0,0 +1,413 @@
#!/bin/bash
# Script to create test data in HomeBox for upgrade testing
# This script creates users, items, attachments, notifiers, locations, and tags
set -e
HOMEBOX_URL="${HOMEBOX_URL:-http://localhost:7745}"
API_URL="${HOMEBOX_URL}/api/v1"
TEST_DATA_FILE="${TEST_DATA_FILE:-/tmp/test-users.json}"
echo "Creating test data in HomeBox at $HOMEBOX_URL"
# Function to make API calls with error handling
api_call() {
local method=$1
local endpoint=$2
local data=$3
local token=$4
if [ -n "$token" ]; then
if [ -n "$data" ]; then
curl -s -X "$method" \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "$data" \
"$API_URL$endpoint"
else
curl -s -X "$method" \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
"$API_URL$endpoint"
fi
else
if [ -n "$data" ]; then
curl -s -X "$method" \
-H "Content-Type: application/json" \
-d "$data" \
"$API_URL$endpoint"
else
curl -s -X "$method" \
-H "Content-Type: application/json" \
"$API_URL$endpoint"
fi
fi
}
# Function to register a user and get token
register_user() {
local email=$1
local name=$2
local password=$3
local group_token=$4
echo "Registering user: $email"
local payload="{\"email\":\"$email\",\"name\":\"$name\",\"password\":\"$password\""
if [ -n "$group_token" ]; then
payload="$payload,\"groupToken\":\"$group_token\""
fi
payload="$payload}"
local response=$(curl -s -X POST \
-H "Content-Type: application/json" \
-d "$payload" \
"$API_URL/users/register")
echo "$response"
}
# Function to login and get token
login_user() {
local email=$1
local password=$2
echo "Logging in user: $email" >&2
local response=$(curl -s -X POST \
-H "Content-Type: application/json" \
-d "{\"username\":\"$email\",\"password\":\"$password\"}" \
"$API_URL/users/login")
echo "$response" | jq -r '.token // empty'
}
# Function to create an item
create_item() {
local token=$1
local name=$2
local description=$3
local location_id=$4
echo "Creating item: $name" >&2
local payload="{\"name\":\"$name\",\"description\":\"$description\""
if [ -n "$location_id" ]; then
payload="$payload,\"locationId\":\"$location_id\""
fi
payload="$payload}"
local response=$(curl -s -X POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "$payload" \
"$API_URL/items")
echo "$response"
}
# Function to create a location
create_location() {
local token=$1
local name=$2
local description=$3
echo "Creating location: $name" >&2
local response=$(curl -s -X POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$name\",\"description\":\"$description\"}" \
"$API_URL/locations")
echo "$response"
}
# Function to create a tag
create_tag() {
local token=$1
local name=$2
local description=$3
echo "Creating tag: $name" >&2
local response=$(curl -s -X POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$name\",\"description\":\"$description\"}" \
"$API_URL/tags")
echo "$response"
}
# Function to create a notifier
create_notifier() {
local token=$1
local name=$2
local url=$3
echo "Creating notifier: $name" >&2
local response=$(curl -s -X POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$name\",\"url\":\"$url\",\"isActive\":true}" \
"$API_URL/groups/notifiers")
echo "$response"
}
# Function to attach a file to an item (creates a dummy attachment)
attach_file_to_item() {
local token=$1
local item_id=$2
local filename=$3
echo "Creating attachment for item: $item_id" >&2
# Create a temporary file with some content
local temp_file=$(mktemp)
echo "This is a test attachment for $filename" > "$temp_file"
local response=$(curl -s -X POST \
-H "Authorization: Bearer $token" \
-F "file=@$temp_file" \
-F "type=attachment" \
-F "name=$filename" \
"$API_URL/items/$item_id/attachments")
rm -f "$temp_file"
echo "$response"
}
# Initialize test data storage
echo "{\"users\":[]}" > "$TEST_DATA_FILE"
echo "=== Step 1: Create first group with 5 users ==="
# Register first user (creates a new group)
user1_response=$(register_user "user1@homebox.test" "User One" "TestPassword123!")
user1_token=$(echo "$user1_response" | jq -r '.token // empty')
group_token=$(echo "$user1_response" | jq -r '.group.inviteToken // empty')
if [ -z "$user1_token" ]; then
echo "Failed to register first user"
echo "Response: $user1_response"
exit 1
fi
echo "First user registered with token. Group token: $group_token"
# Store user1 data
jq --arg email "user1@homebox.test" \
--arg password "TestPassword123!" \
--arg token "$user1_token" \
--arg group "1" \
'.users += [{"email":$email,"password":$password,"token":$token,"group":$group}]' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
# Register 4 more users in the same group
for i in {2..5}; do
echo "Registering user$i in group 1..."
user_response=$(register_user "user${i}@homebox.test" "User $i" "TestPassword123!" "$group_token")
user_token=$(echo "$user_response" | jq -r '.token // empty')
if [ -z "$user_token" ]; then
echo "Failed to register user$i"
echo "Response: $user_response"
else
echo "user$i registered successfully"
# Store user data
jq --arg email "user${i}@homebox.test" \
--arg password "TestPassword123!" \
--arg token "$user_token" \
--arg group "1" \
'.users += [{"email":$email,"password":$password,"token":$token,"group":$group}]' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
fi
done
echo "=== Step 2: Create second group with 2 users ==="
# Register first user of second group
user6_response=$(register_user "user6@homebox.test" "User Six" "TestPassword123!")
user6_token=$(echo "$user6_response" | jq -r '.token // empty')
group2_token=$(echo "$user6_response" | jq -r '.group.inviteToken // empty')
if [ -z "$user6_token" ]; then
echo "Failed to register user6"
echo "Response: $user6_response"
exit 1
fi
echo "user6 registered with token. Group 2 token: $group2_token"
# Store user6 data
jq --arg email "user6@homebox.test" \
--arg password "TestPassword123!" \
--arg token "$user6_token" \
--arg group "2" \
'.users += [{"email":$email,"password":$password,"token":$token,"group":$group}]' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
# Register second user in group 2
user7_response=$(register_user "user7@homebox.test" "User Seven" "TestPassword123!" "$group2_token")
user7_token=$(echo "$user7_response" | jq -r '.token // empty')
if [ -z "$user7_token" ]; then
echo "Failed to register user7"
echo "Response: $user7_response"
else
echo "user7 registered successfully"
# Store user7 data
jq --arg email "user7@homebox.test" \
--arg password "TestPassword123!" \
--arg token "$user7_token" \
--arg group "2" \
'.users += [{"email":$email,"password":$password,"token":$token,"group":$group}]' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
fi
echo "=== Step 3: Create locations for each group ==="
# Create locations for group 1 (using user1's token)
location1=$(create_location "$user1_token" "Living Room" "Main living area")
location1_id=$(echo "$location1" | jq -r '.id // empty')
echo "Created location: Living Room (ID: $location1_id)"
location2=$(create_location "$user1_token" "Garage" "Storage and tools")
location2_id=$(echo "$location2" | jq -r '.id // empty')
echo "Created location: Garage (ID: $location2_id)"
# Create location for group 2 (using user6's token)
location3=$(create_location "$user6_token" "Home Office" "Work from home space")
location3_id=$(echo "$location3" | jq -r '.id // empty')
echo "Created location: Home Office (ID: $location3_id)"
# Store locations
jq --arg loc1 "$location1_id" \
--arg loc2 "$location2_id" \
--arg loc3 "$location3_id" \
'.locations = {"group1":[$loc1,$loc2],"group2":[$loc3]}' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
echo "=== Step 4: Create tags for each group ==="
# Create tags for group 1
tag1=$(create_tag "$user1_token" "Electronics" "Electronic devices")
tag1_id=$(echo "$tag1" | jq -r '.id // empty')
echo "Created tag: Electronics (ID: $tag1_id)"
tag2=$(create_tag "$user1_token" "Important" "High priority items")
tag2_id=$(echo "$tag2" | jq -r '.id // empty')
echo "Created tag: Important (ID: $tag2_id)"
# Create tag for group 2
tag3=$(create_tag "$user6_token" "Work Equipment" "Items for work")
tag3_id=$(echo "$tag3" | jq -r '.id // empty')
echo "Created tag: Work Equipment (ID: $tag3_id)"
# Store tags
jq --arg tag1 "$tag1_id" \
--arg tag2 "$tag2_id" \
--arg tag3 "$tag3_id" \
'.tags = {"group1":[$tag1,$tag2],"group2":[$tag3]}' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
echo "=== Step 5: Create test notifier ==="
# Create notifier for group 1
notifier1=$(create_notifier "$user1_token" "TESTING" "https://example.com/webhook")
notifier1_id=$(echo "$notifier1" | jq -r '.id // empty')
echo "Created notifier: TESTING (ID: $notifier1_id)"
# Store notifier
jq --arg not1 "$notifier1_id" \
'.notifiers = {"group1":[$not1]}' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
echo "=== Step 6: Create items for all users ==="
# Create items for users in group 1
declare -A user_tokens
user_tokens[1]=$user1_token
user_tokens[2]=$(echo "$user1_token") # Users in same group share data, but we'll use user1 token
user_tokens[3]=$(echo "$user1_token")
user_tokens[4]=$(echo "$user1_token")
user_tokens[5]=$(echo "$user1_token")
# Items for group 1 users
echo "Creating items for group 1..."
item1=$(create_item "$user1_token" "Laptop Computer" "Dell XPS 15 for work" "$location1_id")
item1_id=$(echo "$item1" | jq -r '.id // empty')
echo "Created item: Laptop Computer (ID: $item1_id)"
item2=$(create_item "$user1_token" "Power Drill" "DeWalt 20V cordless drill" "$location2_id")
item2_id=$(echo "$item2" | jq -r '.id // empty')
echo "Created item: Power Drill (ID: $item2_id)"
item3=$(create_item "$user1_token" "TV Remote" "Samsung TV remote control" "$location1_id")
item3_id=$(echo "$item3" | jq -r '.id // empty')
echo "Created item: TV Remote (ID: $item3_id)"
item4=$(create_item "$user1_token" "Tool Box" "Red metal tool box with tools" "$location2_id")
item4_id=$(echo "$item4" | jq -r '.id // empty')
echo "Created item: Tool Box (ID: $item4_id)"
item5=$(create_item "$user1_token" "Coffee Maker" "Breville espresso machine" "$location1_id")
item5_id=$(echo "$item5" | jq -r '.id // empty')
echo "Created item: Coffee Maker (ID: $item5_id)"
# Items for group 2 users
echo "Creating items for group 2..."
item6=$(create_item "$user6_token" "Monitor" "27 inch 4K monitor" "$location3_id")
item6_id=$(echo "$item6" | jq -r '.id // empty')
echo "Created item: Monitor (ID: $item6_id)"
item7=$(create_item "$user6_token" "Keyboard" "Mechanical keyboard" "$location3_id")
item7_id=$(echo "$item7" | jq -r '.id // empty')
echo "Created item: Keyboard (ID: $item7_id)"
# Store items
jq --argjson group1_items "[\"$item1_id\",\"$item2_id\",\"$item3_id\",\"$item4_id\",\"$item5_id\"]" \
--argjson group2_items "[\"$item6_id\",\"$item7_id\"]" \
'.items = {"group1":$group1_items,"group2":$group2_items}' \
"$TEST_DATA_FILE" > "$TEST_DATA_FILE.tmp" && mv "$TEST_DATA_FILE.tmp" "$TEST_DATA_FILE"
echo "=== Step 7: Add attachments to items ==="
# Add attachments for group 1 items
echo "Adding attachments to group 1 items..."
attach_file_to_item "$user1_token" "$item1_id" "laptop-receipt.pdf"
attach_file_to_item "$user1_token" "$item1_id" "laptop-warranty.pdf"
attach_file_to_item "$user1_token" "$item2_id" "drill-manual.pdf"
attach_file_to_item "$user1_token" "$item3_id" "remote-guide.pdf"
attach_file_to_item "$user1_token" "$item4_id" "toolbox-inventory.txt"
# Add attachments for group 2 items
echo "Adding attachments to group 2 items..."
attach_file_to_item "$user6_token" "$item6_id" "monitor-receipt.pdf"
attach_file_to_item "$user6_token" "$item7_id" "keyboard-manual.pdf"
echo "=== Test Data Creation Complete ==="
echo "Test data file saved to: $TEST_DATA_FILE"
echo "Summary:"
echo " - Users created: 7 (5 in group 1, 2 in group 2)"
echo " - Locations created: 3"
echo " - Tags created: 3"
echo " - Notifiers created: 1"
echo " - Items created: 7"
echo " - Attachments created: 7"
# Display the test data file for verification
echo ""
echo "Test data:"
cat "$TEST_DATA_FILE" | jq '.'
exit 0

View File

@@ -17,19 +17,17 @@ jobs:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c
with:
go-version: "1.24"
cache-dependency-path: backend/go.mod
- uses: pnpm/action-setup@v2
with:
version: 9.15.3
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
- name: Build Frontend and Copy to Backend
working-directory: frontend
@@ -51,7 +49,7 @@ jobs:
- name: Run GoReleaser
id: releaser
if: startsWith(github.ref, 'refs/tags/')
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
with:
workdir: "backend"
distribution: goreleaser
@@ -75,7 +73,7 @@ jobs:
- name: Run GoReleaser No Release
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
with:
workdir: "backend"
distribution: goreleaser
@@ -93,7 +91,7 @@ jobs:
actions: read # To read the workflow path.
id-token: write # To sign the provenance.
contents: write # To add assets to a release.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
upload-assets: true # upload to a new release
@@ -105,7 +103,7 @@ jobs:
permissions: read-all
steps:
- name: Install the verifier
uses: slsa-framework/slsa-verifier/actions/installer@v2.4.0
uses: slsa-framework/slsa-verifier/actions/installer@ea584f4502babc6f60d9bc799dbbb13c1caa9ee6
- name: Download assets
env:

View File

@@ -12,7 +12,7 @@ jobs:
permissions:
packages: write
steps:
- uses: dataaxiom/ghcr-cleanup-action@v1
- uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4
with:
dry-run: true
delete-ghost-images: true
@@ -32,7 +32,7 @@ jobs:
permissions:
packages: write
steps:
- uses: dataaxiom/ghcr-cleanup-action@v1
- uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4
with:
dry-run: false
delete-untagged: true

View File

@@ -26,25 +26,23 @@ jobs:
# If you do not check out your code, Copilot will do this for you.
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
with:
node-version: "22"
node-version: "24"
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c
with:
go-version: "1.24"
cache-dependency-path: backend/go.mod
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -33,7 +33,7 @@ env:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
@@ -43,10 +43,11 @@ jobs:
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
- linux/arm/v7
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- name: Enable Debug Logs
@@ -56,7 +57,7 @@ jobs:
ACTIONS_STEP_DEBUG: true
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- name: Prepare
run: |
@@ -77,6 +78,15 @@ jobs:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
name=${{ env.GHCR_REPO }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=schedule,pattern=nightly
flavor: |
suffix=-hardened,onlatest=true
- name: Login to Docker Hub
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
@@ -123,7 +133,7 @@ jobs:
annotations: ${{ steps.meta.outputs.annotations }}
- name: Attest platform-specific images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: github.event_name != 'pull_request'
with:
subject-name: ${{ env.GHCR_REPO }}
@@ -216,7 +226,7 @@ jobs:
echo "digest=$digest" >> $GITHUB_OUTPUT
- name: Attest GHCR images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: github.event_name != 'pull_request'
with:
subject-name: ${{ env.GHCR_REPO }}
@@ -240,7 +250,7 @@ jobs:
echo "digest=$digest" >> $GITHUB_OUTPUT
- name: Attest Dockerhub images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
with:
subject-name: docker.io/${{ env.DOCKERHUB_REPO }}

View File

@@ -37,7 +37,7 @@ env:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
@@ -47,10 +47,11 @@ jobs:
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
- linux/arm/v7
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- name: Enable Debug Logs
@@ -60,7 +61,7 @@ jobs:
ACTIONS_STEP_DEBUG: true
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- name: Prepare
run: |
@@ -75,40 +76,49 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
name=${{ env.GHCR_REPO }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=schedule,pattern=nightly
flavor: |
suffix=-rootless,onlatest=true
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392
with:
image: ghcr.io/sysadminsmedia/binfmt:latest
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
with:
driver-opts: |
image=ghcr.io/sysadminsmedia/buildkit:master
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with:
context: . # Explicitly specify the build context
file: ./Dockerfile.rootless # Explicitly specify the Dockerfile
@@ -125,7 +135,7 @@ jobs:
annotations: ${{ steps.meta.outputs.annotations }}
- name: Attest platform-specific images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: github.event_name != 'pull_request'
with:
subject-name: ${{ env.GHCR_REPO }}
@@ -139,7 +149,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
@@ -159,35 +169,35 @@ jobs:
steps:
- name: Download digests
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
with:
driver-opts: |
image=ghcr.io/sysadminsmedia/buildkit:master
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
@@ -218,7 +228,7 @@ jobs:
echo "digest=$digest" >> $GITHUB_OUTPUT
- name: Attest GHCR images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: github.event_name != 'pull_request'
with:
subject-name: ${{ env.GHCR_REPO }}
@@ -242,7 +252,7 @@ jobs:
echo "digest=$digest" >> $GITHUB_OUTPUT
- name: Attest Dockerhub images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
with:
subject-name: docker.io/${{ env.DOCKERHUB_REPO }}

View File

@@ -37,7 +37,7 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
permissions:
contents: read # Allows access to repository contents (read-only)
packages: write # Allows pushing to GHCR
@@ -47,14 +47,15 @@ jobs:
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
- linux/arm/v7
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- name: Prepare
run: |
@@ -70,40 +71,47 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
name=${{ env.GHCR_REPO }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=schedule,pattern=nightly
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392
with:
image: ghcr.io/sysadminsmedia/binfmt:latest
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
with:
driver-opts: |
image=ghcr.io/sysadminsmedia/buildkit:latest
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with:
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
@@ -118,7 +126,7 @@ jobs:
annotations: ${{ steps.meta.outputs.annotations }}
- name: Attest platform-specific images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: github.event_name != 'pull_request'
with:
subject-name: ${{ env.GHCR_REPO }}
@@ -132,7 +140,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
@@ -152,35 +160,35 @@ jobs:
steps:
- name: Download digests
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
with:
driver-opts: |
image=ghcr.io/sysadminsmedia/buildkit:master
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
with:
images: |
name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }}
@@ -209,7 +217,7 @@ jobs:
echo "digest=$digest" >> $GITHUB_OUTPUT
- name: Attest GHCR images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: github.event_name != 'pull_request'
with:
subject-name: ${{ env.GHCR_REPO }}
@@ -233,7 +241,7 @@ jobs:
echo "digest=$digest" >> $GITHUB_OUTPUT
- name: Attest Dockerhub images
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
with:
subject-name: docker.io/${{ env.DOCKERHUB_REPO }}

View File

@@ -1,5 +1,11 @@
name: E2E (Playwright)
permissions:
contents: read
actions: read
checks: write
pull-requests: write
on:
workflow_call:
@@ -15,28 +21,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
fetch-depth: 0
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c
with:
go-version: "1.23"
go-version: "1.24"
cache-dependency-path: backend/go.mod
- uses: actions/setup-node@v4
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
with:
node-version: lts/*
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
- name: Install dependencies
run: pnpm install
@@ -49,7 +53,7 @@ jobs:
- name: Run E2E Tests
run: task test:e2e -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
name: Upload partial Playwright report
if: ${{ !cancelled() }}
with:
@@ -64,20 +68,18 @@ jobs:
name: Merge Playwright Reports
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
with:
node-version: lts/*
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
- name: Install dependencies
run: pnpm install
working-directory: frontend
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
path: frontend/all-blob-reports
pattern: blob-report-*
@@ -88,7 +90,7 @@ jobs:
working-directory: frontend
- name: Upload HTML report
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: html-report--attempt-${{ github.run_attempt }}
path: frontend/playwright-report

50
.github/workflows/issue-gatekeeper.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Issue Gatekeeper
permissions:
issues: write
on:
issues:
types: [ opened ]
jobs:
check-permissions:
runs-on: ubuntu-latest
steps:
- name: Verify Internal Template Use
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
with:
script: |
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const actor = context.payload.sender.login;
// 1. Get user permission level
const { data: perms } = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: actor
});
const isMember = ['admin', 'write'].includes(perms.permission);
const body = context.payload.issue.body || "";
// 2. Check if they used the internal template (or if the issue is blank)
// We detect this by checking for our specific template string or the 'internal' label
const usedInternal = context.payload.issue.labels.some(l => l.name === 'internal');
if (usedInternal && !isMember) {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: `@${actor}, the "Internal" template is restricted to project members. Please use one of the standard bug or feature templates for this repository.`
});
await github.rest.issues.update({
owner,
repo,
issue_number,
state: 'closed'
});
}

View File

@@ -1,5 +1,11 @@
name: Go Build/Test
permissions:
contents: read
actions: read
checks: write
pull-requests: write
on:
workflow_call:
@@ -7,21 +13,21 @@ jobs:
Go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c
with:
go-version: "1.24"
cache-dependency-path: backend/go.mod
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v7
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest

View File

@@ -1,5 +1,11 @@
name: Frontend
permissions:
contents: read
actions: read
checks: write
pull-requests: write
on:
workflow_call:
@@ -9,13 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
fetch-depth: 0
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
- name: Install dependencies
run: pnpm install
@@ -48,28 +52,26 @@ jobs:
--health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
fetch-depth: 0
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c
with:
go-version: "1.23"
go-version: "1.24"
cache-dependency-path: backend/go.mod
- uses: actions/setup-node@v4
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
with:
node-version: 18
node-version: lts/*
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
- name: Install dependencies
run: pnpm install
@@ -99,28 +101,26 @@ jobs:
- 5432:5432
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
fetch-depth: 0
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c
with:
go-version: "1.23"
go-version: "1.24"
cache-dependency-path: backend/go.mod
- uses: actions/setup-node@v4
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
with:
node-version: lts/*
- uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
- name: Install dependencies
run: pnpm install

View File

@@ -1,5 +1,11 @@
name: Pull Request CI
permissions:
contents: read
actions: read
checks: write
pull-requests: write
on:
pull_request:
branches:

View File

@@ -15,12 +15,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
with:
python-version: '3.8'
cache: 'pip'
@@ -44,7 +44,7 @@ jobs:
- name: Create Pull Request
if: env.changed == 'true'
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update-currencies

177
.github/workflows/upgrade-test.yaml vendored Normal file
View File

@@ -0,0 +1,177 @@
#name: HomeBox Upgrade Test
# on:
# schedule:
# Run daily at 2 AM UTC
# - cron: '0 2 * * *'
# workflow_dispatch: # Allow manual trigger
# push:
# branches:
# - main
# paths:
# - '.github/workflows/upgrade-test.yaml'
# - '.github/scripts/upgrade-test/**'
jobs:
upgrade-test:
name: Test Upgrade Path
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read # Read repository contents
packages: read # Pull Docker images from GHCR
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install pnpm
uses: pnpm/action-setup@v3.0.0
with:
version: 9.12.2
- name: Install Playwright
run: |
cd frontend
pnpm install
pnpm exec playwright install --with-deps chromium
- name: Create test data directory
run: |
mkdir -p /tmp/homebox-data-old
mkdir -p /tmp/homebox-data-new
chmod -R 777 /tmp/homebox-data-old
chmod -R 777 /tmp/homebox-data-new
# Step 1: Pull and deploy latest stable version
- name: Pull latest stable HomeBox image
run: |
docker pull ghcr.io/sysadminsmedia/homebox:latest
- name: Start HomeBox (stable version)
run: |
docker run -d \
--name homebox-old \
--restart unless-stopped \
-p 7745:7745 \
-e HBOX_LOG_LEVEL=debug \
-e HBOX_OPTIONS_ALLOW_REGISTRATION=true \
-e TZ=UTC \
-v /tmp/homebox-data-old:/data \
ghcr.io/sysadminsmedia/homebox:latest
# Wait for the service to be ready
timeout 60 bash -c 'until curl -f http://localhost:7745/api/v1/status; do sleep 2; done'
echo "HomeBox stable version is ready"
# Step 2: Create test data
- name: Create test data
run: |
chmod +x .github/scripts/upgrade-test/create-test-data.sh
.github/scripts/upgrade-test/create-test-data.sh
env:
HOMEBOX_URL: http://localhost:7745
- name: Verify initial data creation
run: |
echo "Verifying test data was created..."
# Check if database file exists and has content
if [ -f /tmp/homebox-data-old/homebox.db ]; then
ls -lh /tmp/homebox-data-old/homebox.db
echo "Database file exists"
else
echo "Database file not found!"
exit 1
fi
- name: Stop old HomeBox instance
run: |
docker stop homebox-old
docker rm homebox-old
# Step 3: Build latest version from main branch
- name: Build HomeBox from main branch
run: |
docker build \
--build-arg VERSION=main \
--build-arg COMMIT=${{ github.sha }} \
--build-arg BUILD_TIME="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
-t homebox:test \
-f Dockerfile \
.
# Step 4: Copy data and start new version
- name: Copy data to new location
run: |
cp -r /tmp/homebox-data-old/* /tmp/homebox-data-new/
chmod -R 777 /tmp/homebox-data-new
- name: Start HomeBox (new version)
run: |
docker run -d \
--name homebox-new \
--restart unless-stopped \
-p 7745:7745 \
-e HBOX_LOG_LEVEL=debug \
-e HBOX_OPTIONS_ALLOW_REGISTRATION=true \
-e TZ=UTC \
-v /tmp/homebox-data-new:/data \
homebox:test
# Wait for the service to be ready
timeout 60 bash -c 'until curl -f http://localhost:7745/api/v1/status; do sleep 2; done'
echo "HomeBox new version is ready"
# Step 5: Run verification tests with Playwright
- name: Run verification tests
run: |
cd frontend
TEST_DATA_FILE=/tmp/test-users.json \
E2E_BASE_URL=http://localhost:7745 \
pnpm exec playwright test \
-c ./test/playwright.config.ts \
--project=chromium \
test/upgrade/upgrade-verification.spec.ts
env:
HOMEBOX_URL: http://localhost:7745
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-upgrade-test
path: frontend/playwright-report/
retention-days: 30
- name: Upload test traces
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-traces
path: frontend/test-results/
retention-days: 7
- name: Collect logs on failure
if: failure()
run: |
echo "=== Docker logs for new version ==="
docker logs homebox-new || true
echo "=== Database content ==="
ls -la /tmp/homebox-data-new/ || true
- name: Cleanup
if: always()
run: |
docker stop homebox-new || true
docker rm homebox-new || true
docker rmi homebox:test || true

View File

@@ -8,7 +8,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/sysadminsmedia/homebox/backend v0.0.0-20251212183312-2d1d3d927bfd h1:QULUJSgHc4rSlTjb2qYT6FIgwDWFCqEpnYqc/ltsrkk=
github.com/sysadminsmedia/homebox/backend v0.0.0-20251212183312-2d1d3d927bfd/go.mod h1:jB+tPmHtPDM1VnAjah0gvcRfP/s7c+rtQwpA8cvZD/U=
github.com/sysadminsmedia/homebox/backend v0.0.0-20251228172914-2a6773d1d610 h1:kNLtnxaPaOryBUZ7RgUHPQVWxIExXYR/q9pYCbum5Vk=
github.com/sysadminsmedia/homebox/backend v0.0.0-20251228172914-2a6773d1d610/go.mod h1:9zHHw5TNttw5Kn4Wks+SxwXmJPz6PgGNbnB4BtF1Z4c=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -21,6 +21,9 @@
<img src="https://img.shields.io/mastodon/follow/110749314839831923?domain=infosec.exchange"/>
<img src="https://img.shields.io/lemmy/homebox%40lemmy.world?label=lemmy"/>
</p>
<p align="center" style="width: 100%;">
<a href="https://www.pikapods.com/pods?run=homebox"><img src="https://www.pikapods.com/static/run-button.svg"/></a>
</p>
## What is HomeBox

View File

@@ -17,8 +17,6 @@ builds:
- freebsd
goarch:
- amd64
- "386"
- arm
- arm64
- riscv64
flags:
@@ -28,20 +26,9 @@ builds:
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: "386"
- goos: freebsd
goarch: arm
- goos: freebsd
goarch: "386"
tags:
- >-
{{- if eq .Arch "riscv64" }}nodynamic
{{- else if eq .Arch "arm" }}nodynamic
{{- else if eq .Arch "386" }}nodynamic
{{- else if eq .Os "freebsd" }}nodynamic
{{ end }}
@@ -62,7 +49,6 @@ archives:
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives

View File

@@ -11,7 +11,7 @@ import (
)
func (a *app) SetupDemo() error {
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
csvText := `HB.import_ref,HB.location,HB.tags,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,

View File

@@ -222,7 +222,7 @@ func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc {
}
}
ctrl.bus.Subscribe(eventbus.EventLabelMutation, factory("label.mutation"))
ctrl.bus.Subscribe(eventbus.EventTagMutation, factory("tag.mutation"))
ctrl.bus.Subscribe(eventbus.EventLocationMutation, factory("location.mutation"))
ctrl.bus.Subscribe(eventbus.EventItemMutation, factory("item.mutation"))

View File

@@ -2,6 +2,7 @@ package v1
import (
"context"
"errors"
"net/http"
"github.com/google/uuid"
@@ -9,6 +10,7 @@ import (
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/sysadminsmedia/homebox/backend/internal/sys/validate"
)
@@ -94,3 +96,64 @@ func (ctrl *V1Controller) HandleSetPrimaryPhotos() errchain.HandlerFunc {
func (ctrl *V1Controller) HandleCreateMissingThumbnails() errchain.HandlerFunc {
return actionHandlerFactory("create missing thumbnails", ctrl.repo.Attachments.CreateMissingThumbnails)
}
// WipeInventoryOptions represents the options for wiping inventory
type WipeInventoryOptions struct {
WipeTags bool `json:"wipeTags"`
WipeLocations bool `json:"wipeLocations"`
WipeMaintenance bool `json:"wipeMaintenance"`
}
// HandleWipeInventory godoc
//
// @Summary Wipe Inventory
// @Description Deletes all items in the inventory
// @Tags Actions
// @Produce json
// @Param options body WipeInventoryOptions false "Wipe options"
// @Success 200 {object} ActionAmountResult
// @Router /v1/actions/wipe-inventory [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleWipeInventory() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
return validate.NewRequestError(errors.New("wipe inventory is not allowed in demo mode"), http.StatusForbidden)
}
ctx := services.NewContext(r.Context())
// Check if user is owner
if !ctx.User.IsOwner {
return validate.NewRequestError(errors.New("only group owners can wipe inventory"), http.StatusForbidden)
}
// Parse options from request body
var options WipeInventoryOptions
if err := server.Decode(r, &options); err != nil {
// If no body provided, use default (false for all)
options = WipeInventoryOptions{
WipeTags: false,
WipeLocations: false,
WipeMaintenance: false,
}
}
totalCompleted, err := ctrl.repo.Items.WipeInventory(ctx, ctx.GID, options.WipeTags, options.WipeLocations, options.WipeMaintenance)
if err != nil {
log.Err(err).Str("action_ref", "wipe inventory").Msg("failed to run action")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
// Publish mutation events for wiped resources
if ctrl.bus != nil {
if options.WipeTags {
ctrl.bus.Publish(eventbus.EventTagMutation, eventbus.GroupMutationEvent{GID: ctx.GID})
}
if options.WipeLocations {
ctrl.bus.Publish(eventbus.EventLocationMutation, eventbus.GroupMutationEvent{GID: ctx.GID})
}
}
return server.JSON(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
}
}

View File

@@ -106,7 +106,7 @@ type ItemTemplateCreateItemRequest struct {
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
LocationID uuid.UUID `json:"locationId" validate:"required"`
LabelIDs []uuid.UUID `json:"labelIds"`
TagIDs []uuid.UUID `json:"tagIds"`
Quantity *int `json:"quantity"`
}
@@ -150,7 +150,7 @@ func (ctrl *V1Controller) HandleItemTemplatesCreateItem() errchain.HandlerFunc {
Description: body.Description,
Quantity: quantity,
LocationID: body.LocationID,
LabelIDs: body.LabelIDs,
TagIDs: body.TagIDs,
Insured: template.DefaultInsured,
Manufacturer: template.DefaultManufacturer,
ModelNumber: template.DefaultModelNumber,

View File

@@ -28,7 +28,7 @@ import (
// @Param q query string false "search string"
// @Param page query int false "page number"
// @Param pageSize query int false "items per page"
// @Param labels query []string false "label Ids" collectionFormat(multi)
// @Param tags query []string false "tags Ids" collectionFormat(multi)
// @Param locations query []string false "location Ids" collectionFormat(multi)
// @Param parentIds query []string false "parent Ids" collectionFormat(multi)
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
@@ -59,8 +59,8 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
PageSize: queryIntOrNegativeOne(params.Get("pageSize")),
Search: params.Get("q"),
LocationIDs: queryUUIDList(params, "locations"),
LabelIDs: queryUUIDList(params, "labels"),
NegateLabels: queryBool(params.Get("negateLabels")),
TagIDs: queryUUIDList(params, "tags"),
NegateTags: queryBool(params.Get("negateTags")),
OnlyWithoutPhoto: queryBool(params.Get("onlyWithoutPhoto")),
OnlyWithPhoto: queryBool(params.Get("onlyWithPhoto")),
ParentItemIDs: queryUUIDList(params, "parentIds"),

View File

@@ -1,102 +0,0 @@
package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/httpkit/errchain"
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"github.com/sysadminsmedia/homebox/backend/internal/web/adapters"
)
// HandleLabelsGetAll godoc
//
// @Summary Get All Labels
// @Tags Labels
// @Produce json
// @Success 200 {object} []repo.LabelOut
// @Router /v1/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.LabelSummary, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Labels.GetAll(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleLabelsCreate godoc
//
// @Summary Create Label
// @Tags Labels
// @Produce json
// @Param payload body repo.LabelCreate true "Label Data"
// @Success 200 {object} repo.LabelSummary
// @Router /v1/labels [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc {
fn := func(r *http.Request, data repo.LabelCreate) (repo.LabelOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Labels.Create(auth, auth.GID, data)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleLabelDelete godocs
//
// @Summary Delete Label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 204
// @Router /v1/labels/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleLabelGet godocs
//
// @Summary Get Label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.LabelOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Labels.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleLabelUpdate godocs
//
// @Summary Update Label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) {
auth := services.NewContext(r.Context())
data.ID = ID
return ctrl.repo.Labels.UpdateByGroup(auth, auth.GID, data)
}
return adapters.ActionID("id", fn, http.StatusOK)
}

View File

@@ -29,18 +29,18 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupStatisticsLabels godoc
// HandleGroupStatisticsTags godoc
//
// @Summary Get Label Statistics
// @Summary Get Tags Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/labels [GET]
// @Router /v1/groups/statistics/tags [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc {
func (ctrl *V1Controller) HandleGroupStatisticsTags() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID)
return ctrl.repo.Groups.StatsTagsByPurchasePrice(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)

View File

@@ -0,0 +1,102 @@
package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/httpkit/errchain"
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"github.com/sysadminsmedia/homebox/backend/internal/web/adapters"
)
// HandleTagsGetAll godoc
//
// @Summary Get All Tags
// @Tags Tags
// @Produce json
// @Success 200 {object} []repo.TagOut
// @Router /v1/tags [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleTagsGetAll() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.TagSummary, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Tags.GetAll(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleTagsCreate godoc
//
// @Summary Create Tag
// @Tags Tags
// @Produce json
// @Param payload body repo.TagCreate true "Tag Data"
// @Success 200 {object} repo.TagSummary
// @Router /v1/tags [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleTagsCreate() errchain.HandlerFunc {
fn := func(r *http.Request, data repo.TagCreate) (repo.TagOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Tags.Create(auth, auth.GID, data)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleTagDelete godocs
//
// @Summary Delete Tag
// @Tags Tags
// @Produce json
// @Param id path string true "Tag ID"
// @Success 204
// @Router /v1/tags/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleTagDelete() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.Tags.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleTagGet godocs
//
// @Summary Get Tag
// @Tags Tags
// @Produce json
// @Param id path string true "Tag ID"
// @Success 200 {object} repo.TagOut
// @Router /v1/tags/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleTagGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.TagOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Tags.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleTagUpdate godocs
//
// @Summary Update Tag
// @Tags Tags
// @Produce json
// @Param id path string true "Tag ID"
// @Success 200 {object} repo.TagOut
// @Router /v1/tags/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleTagUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, data repo.TagUpdate) (repo.TagOut, error) {
auth := services.NewContext(r.Context())
data.ID = ID
return ctrl.repo.Tags.UpdateByGroup(auth, auth.GID, data)
}
return adapters.ActionID("id", fn, http.StatusOK)
}

View File

@@ -108,7 +108,7 @@ func run(cfg *config.Config) error {
return err
}
if strings.ToLower(cfg.Database.Driver) == "postgres" {
if strings.ToLower(cfg.Database.Driver) == config.DriverPostgres {
if !validatePostgresSSLMode(cfg.Database.SslMode) {
log.Error().Str("sslmode", cfg.Database.SslMode).Msg("invalid sslmode")
return fmt.Errorf("invalid sslmode: %s", cfg.Database.SslMode)

View File

@@ -97,7 +97,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
r.Get("/groups/statistics", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
r.Get("/groups/statistics/purchase-price", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
r.Get("/groups/statistics/locations", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
r.Get("/groups/statistics/labels", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
r.Get("/groups/statistics/tags", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsTags(), userMW...))
// TODO: I don't like /groups being the URL for users
r.Get("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
@@ -108,6 +108,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
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.Post("/actions/wipe-inventory", chain.ToHandlerFunc(v1Ctrl.HandleWipeInventory(), userMW...))
r.Get("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
r.Post("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
@@ -116,11 +117,11 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
r.Put("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
r.Delete("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
r.Get("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
r.Post("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
r.Get("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
r.Put("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
r.Delete("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
r.Get("/tags", chain.ToHandlerFunc(v1Ctrl.HandleTagsGetAll(), userMW...))
r.Post("/tags", chain.ToHandlerFunc(v1Ctrl.HandleTagsCreate(), userMW...))
r.Get("/tags/{id}", chain.ToHandlerFunc(v1Ctrl.HandleTagGet(), userMW...))
r.Put("/tags/{id}", chain.ToHandlerFunc(v1Ctrl.HandleTagUpdate(), userMW...))
r.Delete("/tags/{id}", chain.ToHandlerFunc(v1Ctrl.HandleTagDelete(), userMW...))
r.Get("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
r.Post("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))

View File

@@ -41,7 +41,7 @@ func setupStorageDir(cfg *config.Config) error {
func setupDatabaseURL(cfg *config.Config) (string, error) {
databaseURL := ""
switch strings.ToLower(cfg.Database.Driver) {
case "sqlite3":
case config.DriverSqlite3:
databaseURL = cfg.Database.SqlitePath
dbFilePath := strings.Split(cfg.Database.SqlitePath, "?")[0]
dbDir := filepath.Dir(dbFilePath)
@@ -49,7 +49,7 @@ func setupDatabaseURL(cfg *config.Config) (string, error) {
log.Error().Err(err).Str("path", dbDir).Msg("failed to create SQLite database directory")
return "", fmt.Errorf("failed to create SQLite database directory: %w", err)
}
case "postgres":
case config.DriverPostgres:
databaseURL = fmt.Sprintf("host=%s port=%s dbname=%s sslmode=%s", cfg.Database.Host, cfg.Database.Port, cfg.Database.Database, cfg.Database.SslMode)
if cfg.Database.Username != "" {
databaseURL += fmt.Sprintf(" user=%s", cfg.Database.Username)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,27 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/v1.ActionAmountResult"
/v1/actions/wipe-inventory:
post:
security:
- Bearer: []
description: Deletes all items in the inventory
tags:
- Actions
summary: Wipe Inventory
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/v1.WipeInventoryOptions"
description: Wipe options
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/v1.ActionAmountResult"
/v1/actions/zero-item-time-fields:
post:
security:
@@ -184,22 +205,6 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/repo.GroupStatistics"
/v1/groups/statistics/labels:
get:
security:
- Bearer: []
tags:
- Statistics
summary: Get Label Statistics
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/repo.TotalsByOrganizer"
/v1/groups/statistics/locations:
get:
security:
@@ -241,6 +246,22 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/repo.ValueOverTime"
/v1/groups/statistics/tags:
get:
security:
- Bearer: []
tags:
- Statistics
summary: Get Tags Statistics
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/repo.TotalsByOrganizer"
/v1/items:
get:
security:
@@ -264,8 +285,8 @@ paths:
in: query
schema:
type: integer
- description: label Ids
name: labels
- description: tags Ids
name: tags
in: query
explode: true
schema:
@@ -798,99 +819,6 @@ paths:
application/json:
schema:
type: string
/v1/labels:
get:
security:
- Bearer: []
tags:
- Labels
summary: Get All Labels
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/repo.LabelOut"
post:
security:
- Bearer: []
tags:
- Labels
summary: Create Label
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/repo.LabelCreate"
description: Label Data
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/repo.LabelSummary"
"/v1/labels/{id}":
get:
security:
- Bearer: []
tags:
- Labels
summary: Get Label
parameters:
- description: Label ID
name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/repo.LabelOut"
put:
security:
- Bearer: []
tags:
- Labels
summary: Update Label
parameters:
- description: Label ID
name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/repo.LabelOut"
delete:
security:
- Bearer: []
tags:
- Labels
summary: Delete Label
parameters:
- description: Label ID
name: id
in: path
required: true
schema:
type: string
responses:
"204":
description: No Content
/v1/locations:
get:
security:
@@ -1257,6 +1185,99 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/v1.APISummary"
/v1/tags:
get:
security:
- Bearer: []
tags:
- Tags
summary: Get All Tags
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/repo.TagOut"
post:
security:
- Bearer: []
tags:
- Tags
summary: Create Tag
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/repo.TagCreate"
description: Tag Data
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/repo.TagSummary"
"/v1/tags/{id}":
get:
security:
- Bearer: []
tags:
- Tags
summary: Get Tag
parameters:
- description: Tag ID
name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/repo.TagOut"
put:
security:
- Bearer: []
tags:
- Tags
summary: Update Tag
parameters:
- description: Tag ID
name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/repo.TagOut"
delete:
security:
- Bearer: []
tags:
- Tags
summary: Delete Tag
parameters:
- description: Tag ID
name: id
in: path
required: true
schema:
type: string
responses:
"204":
description: No Content
/v1/templates:
get:
security:
@@ -1763,11 +1784,6 @@ components:
type: array
items:
$ref: "#/components/schemas/ent.Item"
labels:
description: Labels holds the value of the labels edge.
type: array
items:
$ref: "#/components/schemas/ent.Label"
locations:
description: Locations holds the value of the locations edge.
type: array
@@ -1778,6 +1794,11 @@ components:
type: array
items:
$ref: "#/components/schemas/ent.Notifier"
tags:
description: Tags holds the value of the tags edge.
type: array
items:
$ref: "#/components/schemas/ent.Tag"
users:
description: Users holds the value of the users edge.
type: array
@@ -1928,11 +1949,6 @@ components:
description: Group holds the value of the group edge.
allOf:
- $ref: "#/components/schemas/ent.Group"
label:
description: Label holds the value of the label edge.
type: array
items:
$ref: "#/components/schemas/ent.Label"
location:
description: Location holds the value of the location edge.
allOf:
@@ -1946,6 +1962,11 @@ components:
description: Parent holds the value of the parent edge.
allOf:
- $ref: "#/components/schemas/ent.Item"
tag:
description: Tag holds the value of the tag edge.
type: array
items:
$ref: "#/components/schemas/ent.Tag"
ent.ItemField:
type: object
properties:
@@ -2006,11 +2027,6 @@ components:
default_insured:
description: DefaultInsured holds the value of the "default_insured" field.
type: boolean
default_label_ids:
description: Default label IDs for items created from this template
type: array
items:
type: string
default_lifetime_warranty:
description: DefaultLifetimeWarranty holds the value of the
"default_lifetime_warranty" field.
@@ -2028,6 +2044,11 @@ components:
default_quantity:
description: DefaultQuantity holds the value of the "default_quantity" field.
type: integer
default_tag_ids:
description: Default tag IDs for items created from this template
type: array
items:
type: string
default_warranty_details:
description: DefaultWarrantyDetails holds the value of the
"default_warranty_details" field.
@@ -2081,46 +2102,6 @@ components:
description: Location holds the value of the location edge.
allOf:
- $ref: "#/components/schemas/ent.Location"
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.
The values are being populated by the LabelQuery when eager-loading is set.
allOf:
- $ref: "#/components/schemas/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: "#/components/schemas/ent.Group"
items:
description: Items holds the value of the items edge.
type: array
items:
$ref: "#/components/schemas/ent.Item"
ent.Location:
type: object
properties:
@@ -2253,6 +2234,46 @@ components:
description: User holds the value of the user edge.
allOf:
- $ref: "#/components/schemas/ent.User"
ent.Tag:
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.
The values are being populated by the TagQuery when eager-loading is set.
allOf:
- $ref: "#/components/schemas/ent.TagEdges"
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.TagEdges:
type: object
properties:
group:
description: Group holds the value of the group edge.
allOf:
- $ref: "#/components/schemas/ent.Group"
items:
description: Items holds the value of the items edge.
type: array
items:
$ref: "#/components/schemas/ent.Item"
ent.TemplateField:
type: object
properties:
@@ -2417,10 +2438,10 @@ components:
type: number
totalItems:
type: integer
totalLabels:
type: integer
totalLocations:
type: integer
totalTags:
type: integer
totalUsers:
type: integer
totalWithWarranty:
@@ -2470,10 +2491,6 @@ components:
description:
type: string
maxLength: 1000
labelIds:
type: array
items:
type: string
locationId:
description: Edges
type: string
@@ -2486,6 +2503,10 @@ components:
nullable: true
quantity:
type: integer
tagIds:
type: array
items:
type: string
repo.ItemField:
type: object
properties:
@@ -2529,10 +2550,6 @@ components:
nullable: true
insured:
type: boolean
labels:
type: array
items:
$ref: "#/components/schemas/repo.LabelSummary"
lifetimeWarranty:
description: Warranty
type: boolean
@@ -2578,6 +2595,10 @@ components:
type: string
syncChildItemsLocations:
type: boolean
tags:
type: array
items:
$ref: "#/components/schemas/repo.TagSummary"
thumbnailId:
type: string
x-omitempty: true
@@ -2593,12 +2614,6 @@ components:
properties:
id:
type: string
labelIds:
type: array
items:
type: string
x-omitempty: true
nullable: true
locationId:
type: string
x-omitempty: true
@@ -2607,6 +2622,12 @@ components:
type: integer
x-omitempty: true
nullable: true
tagIds:
type: array
items:
type: string
x-omitempty: true
nullable: true
repo.ItemPath:
type: object
properties:
@@ -2636,10 +2657,6 @@ components:
nullable: true
insured:
type: boolean
labels:
type: array
items:
$ref: "#/components/schemas/repo.LabelSummary"
location:
description: Edges
allOf:
@@ -2655,6 +2672,10 @@ components:
soldTime:
description: Sale details
type: string
tags:
type: array
items:
$ref: "#/components/schemas/repo.TagSummary"
thumbnailId:
type: string
x-omitempty: true
@@ -2672,15 +2693,10 @@ components:
nullable: true
defaultInsured:
type: boolean
defaultLabelIds:
type: array
items:
type: string
nullable: true
defaultLifetimeWarranty:
type: boolean
defaultLocationId:
description: Default location and labels
description: Default location and tags
type: string
nullable: true
defaultManufacturer:
@@ -2699,6 +2715,11 @@ components:
description: Default values for items
type: integer
nullable: true
defaultTagIds:
type: array
items:
type: string
nullable: true
defaultWarrantyDetails:
type: string
maxLength: 1000
@@ -2734,14 +2755,10 @@ components:
type: string
defaultInsured:
type: boolean
defaultLabels:
type: array
items:
$ref: "#/components/schemas/repo.TemplateLabelSummary"
defaultLifetimeWarranty:
type: boolean
defaultLocation:
description: Default location and labels
description: Default location and tags
allOf:
- $ref: "#/components/schemas/repo.TemplateLocationSummary"
defaultManufacturer:
@@ -2753,6 +2770,10 @@ components:
defaultQuantity:
description: Default values for items
type: integer
defaultTags:
type: array
items:
$ref: "#/components/schemas/repo.TemplateTagSummary"
defaultWarrantyDetails:
type: string
description:
@@ -2801,15 +2822,10 @@ components:
nullable: true
defaultInsured:
type: boolean
defaultLabelIds:
type: array
items:
type: string
nullable: true
defaultLifetimeWarranty:
type: boolean
defaultLocationId:
description: Default location and labels
description: Default location and tags
type: string
nullable: true
defaultManufacturer:
@@ -2828,6 +2844,11 @@ components:
description: Default values for items
type: integer
nullable: true
defaultTagIds:
type: array
items:
type: string
nullable: true
defaultWarrantyDetails:
type: string
maxLength: 1000
@@ -2884,10 +2905,6 @@ components:
type: string
insured:
type: boolean
labelIds:
type: array
items:
type: string
lifetimeWarranty:
description: Warranty
type: boolean
@@ -2938,54 +2955,14 @@ components:
maxLength: 255
syncChildItemsLocations:
type: boolean
tagIds:
type: array
items:
type: string
warrantyDetails:
type: string
warrantyExpires:
type: string
repo.LabelCreate:
type: object
required:
- name
properties:
color:
type: string
description:
type: string
maxLength: 1000
name:
type: string
maxLength: 255
minLength: 1
repo.LabelOut:
type: object
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
repo.LabelSummary:
type: object
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
repo.LocationCreate:
type: object
properties:
@@ -3193,6 +3170,50 @@ components:
type: integer
total:
type: integer
repo.TagCreate:
type: object
required:
- name
properties:
color:
type: string
description:
type: string
maxLength: 1000
name:
type: string
maxLength: 255
minLength: 1
repo.TagOut:
type: object
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
repo.TagSummary:
type: object
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
repo.TemplateField:
type: object
properties:
@@ -3204,14 +3225,14 @@ components:
type: string
type:
type: string
repo.TemplateLabelSummary:
repo.TemplateLocationSummary:
type: object
properties:
id:
type: string
name:
type: string
repo.TemplateLocationSummary:
repo.TemplateTagSummary:
type: object
properties:
id:
@@ -3406,10 +3427,6 @@ components:
description:
type: string
maxLength: 1000
labelIds:
type: array
items:
type: string
locationId:
type: string
name:
@@ -3418,6 +3435,10 @@ components:
minLength: 1
quantity:
type: integer
tagIds:
type: array
items:
type: string
v1.LoginForm:
type: object
properties:
@@ -3449,6 +3470,15 @@ components:
type: string
token:
type: string
v1.WipeInventoryOptions:
type: object
properties:
wipeLocations:
type: boolean
wipeMaintenance:
type: boolean
wipeTags:
type: boolean
v1.Wrapped:
type: object
properties:

File diff suppressed because it is too large Load Diff

View File

@@ -189,11 +189,6 @@ definitions:
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:
@@ -204,6 +199,11 @@ definitions:
items:
$ref: '#/definitions/ent.Notifier'
type: array
tags:
description: Tags holds the value of the tags edge.
items:
$ref: '#/definitions/ent.Tag'
type: array
users:
description: Users holds the value of the users edge.
items:
@@ -352,11 +352,6 @@ definitions:
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'
@@ -371,6 +366,11 @@ definitions:
allOf:
- $ref: '#/definitions/ent.Item'
description: Parent holds the value of the parent edge.
tag:
description: Tag holds the value of the tag edge.
items:
$ref: '#/definitions/ent.Tag'
type: array
type: object
ent.ItemField:
properties:
@@ -430,11 +430,6 @@ definitions:
default_insured:
description: DefaultInsured holds the value of the "default_insured" field.
type: boolean
default_label_ids:
description: Default label IDs for items created from this template
items:
type: string
type: array
default_lifetime_warranty:
description: DefaultLifetimeWarranty holds the value of the "default_lifetime_warranty"
field.
@@ -452,6 +447,11 @@ definitions:
default_quantity:
description: DefaultQuantity holds the value of the "default_quantity" field.
type: integer
default_tag_ids:
description: Default tag IDs for items created from this template
items:
type: string
type: array
default_warranty_details:
description: DefaultWarrantyDetails holds the value of the "default_warranty_details"
field.
@@ -505,45 +505,6 @@ definitions:
- $ref: '#/definitions/ent.Location'
description: Location holds the value of the location 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:
@@ -673,6 +634,45 @@ definitions:
- $ref: '#/definitions/ent.User'
description: User holds the value of the user edge.
type: object
ent.Tag:
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.TagEdges'
description: |-
Edges holds the relations/edges for other nodes in the graph.
The values are being populated by the TagQuery 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.TagEdges:
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.TemplateField:
properties:
created_at:
@@ -834,10 +834,10 @@ definitions:
type: number
totalItems:
type: integer
totalLabels:
type: integer
totalLocations:
type: integer
totalTags:
type: integer
totalUsers:
type: integer
totalWithWarranty:
@@ -885,10 +885,6 @@ definitions:
description:
maxLength: 1000
type: string
labelIds:
items:
type: string
type: array
locationId:
description: Edges
type: string
@@ -901,6 +897,10 @@ definitions:
x-nullable: true
quantity:
type: integer
tagIds:
items:
type: string
type: array
required:
- name
type: object
@@ -946,10 +946,6 @@ definitions:
x-omitempty: true
insured:
type: boolean
labels:
items:
$ref: '#/definitions/repo.LabelSummary'
type: array
lifetimeWarranty:
description: Warranty
type: boolean
@@ -995,6 +991,10 @@ definitions:
type: string
syncChildItemsLocations:
type: boolean
tags:
items:
$ref: '#/definitions/repo.TagSummary'
type: array
thumbnailId:
type: string
x-nullable: true
@@ -1010,12 +1010,6 @@ definitions:
properties:
id:
type: string
labelIds:
items:
type: string
type: array
x-nullable: true
x-omitempty: true
locationId:
type: string
x-nullable: true
@@ -1024,6 +1018,12 @@ definitions:
type: integer
x-nullable: true
x-omitempty: true
tagIds:
items:
type: string
type: array
x-nullable: true
x-omitempty: true
type: object
repo.ItemPath:
properties:
@@ -1053,10 +1053,6 @@ definitions:
x-omitempty: true
insured:
type: boolean
labels:
items:
$ref: '#/definitions/repo.LabelSummary'
type: array
location:
allOf:
- $ref: '#/definitions/repo.LocationSummary'
@@ -1072,6 +1068,10 @@ definitions:
soldTime:
description: Sale details
type: string
tags:
items:
$ref: '#/definitions/repo.TagSummary'
type: array
thumbnailId:
type: string
x-nullable: true
@@ -1087,15 +1087,10 @@ definitions:
x-nullable: true
defaultInsured:
type: boolean
defaultLabelIds:
items:
type: string
type: array
x-nullable: true
defaultLifetimeWarranty:
type: boolean
defaultLocationId:
description: Default location and labels
description: Default location and tags
type: string
x-nullable: true
defaultManufacturer:
@@ -1114,6 +1109,11 @@ definitions:
description: Default values for items
type: integer
x-nullable: true
defaultTagIds:
items:
type: string
type: array
x-nullable: true
defaultWarrantyDetails:
maxLength: 1000
type: string
@@ -1151,16 +1151,12 @@ definitions:
type: string
defaultInsured:
type: boolean
defaultLabels:
items:
$ref: '#/definitions/repo.TemplateLabelSummary'
type: array
defaultLifetimeWarranty:
type: boolean
defaultLocation:
allOf:
- $ref: '#/definitions/repo.TemplateLocationSummary'
description: Default location and labels
description: Default location and tags
defaultManufacturer:
type: string
defaultModelNumber:
@@ -1170,6 +1166,10 @@ definitions:
defaultQuantity:
description: Default values for items
type: integer
defaultTags:
items:
$ref: '#/definitions/repo.TemplateTagSummary'
type: array
defaultWarrantyDetails:
type: string
description:
@@ -1216,15 +1216,10 @@ definitions:
x-nullable: true
defaultInsured:
type: boolean
defaultLabelIds:
items:
type: string
type: array
x-nullable: true
defaultLifetimeWarranty:
type: boolean
defaultLocationId:
description: Default location and labels
description: Default location and tags
type: string
x-nullable: true
defaultManufacturer:
@@ -1243,6 +1238,11 @@ definitions:
description: Default values for items
type: integer
x-nullable: true
defaultTagIds:
items:
type: string
type: array
x-nullable: true
defaultWarrantyDetails:
maxLength: 1000
type: string
@@ -1299,10 +1299,6 @@ definitions:
type: string
insured:
type: boolean
labelIds:
items:
type: string
type: array
lifetimeWarranty:
description: Warranty
type: boolean
@@ -1353,6 +1349,10 @@ definitions:
type: string
syncChildItemsLocations:
type: boolean
tagIds:
items:
type: string
type: array
warrantyDetails:
type: string
warrantyExpires:
@@ -1360,50 +1360,6 @@ definitions:
required:
- name
type: object
repo.LabelCreate:
properties:
color:
type: string
description:
maxLength: 1000
type: string
name:
maxLength: 255
minLength: 1
type: string
required:
- name
type: object
repo.LabelOut:
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
type: object
repo.LabelSummary:
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
type: object
repo.LocationCreate:
properties:
description:
@@ -1611,6 +1567,50 @@ definitions:
total:
type: integer
type: object
repo.TagCreate:
properties:
color:
type: string
description:
maxLength: 1000
type: string
name:
maxLength: 255
minLength: 1
type: string
required:
- name
type: object
repo.TagOut:
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
type: object
repo.TagSummary:
properties:
color:
type: string
createdAt:
type: string
description:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
type: object
repo.TemplateField:
properties:
id:
@@ -1622,14 +1622,14 @@ definitions:
type:
type: string
type: object
repo.TemplateLabelSummary:
repo.TemplateLocationSummary:
properties:
id:
type: string
name:
type: string
type: object
repo.TemplateLocationSummary:
repo.TemplateTagSummary:
properties:
id:
type: string
@@ -1820,10 +1820,6 @@ definitions:
description:
maxLength: 1000
type: string
labelIds:
items:
type: string
type: array
locationId:
type: string
name:
@@ -1832,6 +1828,10 @@ definitions:
type: string
quantity:
type: integer
tagIds:
items:
type: string
type: array
required:
- locationId
- name
@@ -1867,6 +1867,15 @@ definitions:
token:
type: string
type: object
v1.WipeInventoryOptions:
properties:
wipeLocations:
type: boolean
wipeMaintenance:
type: boolean
wipeTags:
type: boolean
type: object
v1.Wrapped:
properties:
item: {}
@@ -1947,6 +1956,27 @@ paths:
summary: Set Primary Photos
tags:
- Actions
/v1/actions/wipe-inventory:
post:
description: Deletes all items in the inventory
parameters:
- description: Wipe options
in: body
name: options
schema:
$ref: '#/definitions/v1.WipeInventoryOptions'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/v1.ActionAmountResult'
security:
- Bearer: []
summary: Wipe Inventory
tags:
- Actions
/v1/actions/zero-item-time-fields:
post:
description: Resets all item date fields to the beginning of the day
@@ -2063,22 +2093,6 @@ paths:
summary: Get Group Statistics
tags:
- Statistics
/v1/groups/statistics/labels:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.TotalsByOrganizer'
type: array
security:
- Bearer: []
summary: Get Label Statistics
tags:
- Statistics
/v1/groups/statistics/locations:
get:
produces:
@@ -2118,6 +2132,22 @@ paths:
summary: Get Purchase Price Statistics
tags:
- Statistics
/v1/groups/statistics/tags:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.TotalsByOrganizer'
type: array
security:
- Bearer: []
summary: Get Tags Statistics
tags:
- Statistics
/v1/items:
get:
parameters:
@@ -2134,11 +2164,11 @@ paths:
name: pageSize
type: integer
- collectionFormat: multi
description: label Ids
description: tags Ids
in: query
items:
type: string
name: labels
name: tags
type: array
- collectionFormat: multi
description: location Ids
@@ -2632,98 +2662,6 @@ paths:
summary: Get Location label
tags:
- Locations
/v1/labels:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.LabelOut'
type: array
security:
- Bearer: []
summary: Get All Labels
tags:
- Labels
post:
parameters:
- description: Label Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.LabelCreate'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.LabelSummary'
security:
- Bearer: []
summary: Create Label
tags:
- Labels
/v1/labels/{id}:
delete:
parameters:
- description: Label ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
security:
- Bearer: []
summary: Delete Label
tags:
- Labels
get:
parameters:
- description: Label ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.LabelOut'
security:
- Bearer: []
summary: Get Label
tags:
- Labels
put:
parameters:
- description: Label ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.LabelOut'
security:
- Bearer: []
summary: Update Label
tags:
- Labels
/v1/locations:
get:
parameters:
@@ -3078,6 +3016,98 @@ paths:
summary: Application Info
tags:
- Base
/v1/tags:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.TagOut'
type: array
security:
- Bearer: []
summary: Get All Tags
tags:
- Tags
post:
parameters:
- description: Tag Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.TagCreate'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.TagSummary'
security:
- Bearer: []
summary: Create Tag
tags:
- Tags
/v1/tags/{id}:
delete:
parameters:
- description: Tag ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
security:
- Bearer: []
summary: Delete Tag
tags:
- Tags
get:
parameters:
- description: Tag ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.TagOut'
security:
- Bearer: []
summary: Get Tag
tags:
- Tags
put:
parameters:
- description: Tag ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.TagOut'
security:
- Bearer: []
summary: Update Tag
tags:
- Tags
/v1/templates:
get:
produces:

View File

@@ -10,31 +10,22 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
cloud.google.com/go/pubsub v1.50.0 h1:hnYpOIxVlgVD1Z8LN7est4DQZK3K6tvZNurZjIVjUe0=
cloud.google.com/go/pubsub v1.50.0/go.mod h1:Di2Y+nqXBpIS+dXUEJPQzLh8PbIQZMLE9IVUFhf2zmM=
cloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviBM=
cloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk=
cloud.google.com/go/pubsub/v2 v2.2.1 h1:3brZcshL3fIiD1qOxAE2QW9wxsfjioy014x4yC9XuYI=
cloud.google.com/go/pubsub/v2 v2.2.1/go.mod h1:O5f0KHG9zDheZAd3z5rlCRhxt2JQtB+t/IYLKK3Bpvw=
cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=
cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk=
@@ -88,8 +79,6 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/ardanlabs/conf/v3 v3.9.0 h1:aRBYHeD39/OkuaEXYIEoi4wvF3OnS7jUAPxXyLfEu20=
github.com/ardanlabs/conf/v3 v3.9.0/go.mod h1:XlL9P0quWP4m1weOVFmlezabinbZLI05niDof/+Ochk=
github.com/ardanlabs/conf/v3 v3.10.0 h1:qIrJ/WBmH/hFQ/IX4xH9LX9LzwK44T9aEOy78M+4S+0=
github.com/ardanlabs/conf/v3 v3.10.0/go.mod h1:XlL9P0quWP4m1weOVFmlezabinbZLI05niDof/+Ochk=
github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk=
@@ -183,14 +172,10 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP
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.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gen2brain/avif v0.4.4 h1:Ga/ss7qcWWQm2bxFpnjYjhJsNfZrWs5RsyklgFjKRSE=
github.com/gen2brain/avif v0.4.4/go.mod h1:/XCaJcjZraQwKVhpu9aEd9aLOssYOawLvhMBtmHVGqk=
github.com/gen2brain/heic v0.4.6 h1:sNh3mfaEZLmDJnFc5WoLxCzh/wj5GwfJScPfvF5CNJE=
github.com/gen2brain/heic v0.4.6/go.mod h1:ECnpqbqLu0qSje4KSNWUUDK47UPXPzl80T27GWGEL5I=
github.com/gen2brain/heic v0.4.7 h1:xw/e9R3HdIvb+uEhRDMRJdviYnB3ODe/VwL8SYLaMGc=
github.com/gen2brain/heic v0.4.7/go.mod h1:ECnpqbqLu0qSje4KSNWUUDK47UPXPzl80T27GWGEL5I=
github.com/gen2brain/jpegxl v0.4.5 h1:TWpVEn5xkIfsswzkjHBArd0Cc9AE0tbjBSoa0jDsrbo=
@@ -210,16 +195,10 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8=
github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=
github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=
github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc=
github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
@@ -249,8 +228,6 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
@@ -290,8 +267,6 @@ github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
@@ -350,6 +325,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
@@ -362,8 +339,6 @@ 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.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
@@ -374,14 +349,14 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI=
github.com/olahol/melody v1.4.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
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/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
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/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=
github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -418,6 +393,10 @@ github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAX
github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -437,8 +416,6 @@ 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.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
@@ -476,26 +453,16 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -516,21 +483,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -540,18 +499,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -567,8 +520,6 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -578,8 +529,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
@@ -587,8 +536,6 @@ golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -597,28 +544,16 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA=
google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4=
google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc=
google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww=
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs=
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -640,8 +575,6 @@ modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.1 h1:bFaqOaa5/zbWYJo8aW0tXPX21hXsngG2M7mckCnFSVk=
modernc.org/libc v1.67.1/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
modernc.org/libc v1.67.2 h1:ZbNmly1rcbjhot5jlOZG0q4p5VwFfjwWqZ5rY2xxOXo=
modernc.org/libc v1.67.2/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
@@ -652,8 +585,6 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY=
modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/sqlite v1.41.0 h1:bJXddp4ZpsqMsNN1vS0jWo4IJTZzb8nWpcgvyCFG9Ck=
modernc.org/sqlite v1.41.0/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=

View File

@@ -1,4 +1,4 @@
HB.name,HB.asset_id,HB.location,HB.labels
HB.name,HB.asset_id,HB.location,HB.tags
Item 1,1,Path / To / Location 1,L1 ; L2 ; L3
Item 2,000-002,Path /To/ Location 2,L1;L2;L3
Item 3,1000-003,Path / To /Location 3 , L1;L2; L3
1 HB.name HB.asset_id HB.location HB.labels HB.tags
2 Item 1 1 Path / To / Location 1 L1 ; L2 ; L3 L1 ; L2 ; L3
3 Item 2 000-002 Path /To/ Location 2 L1;L2;L3 L1;L2;L3
4 Item 3 1000-003 Path / To /Location 3 L1;L2; L3 L1;L2; L3

View File

@@ -11,7 +11,7 @@ import (
type Event string
const (
EventLabelMutation Event = "label.mutation"
EventTagMutation Event = "tags.mutation"
EventLocationMutation Event = "location.mutation"
EventItemMutation Event = "item.mutation"
)
@@ -37,7 +37,7 @@ func New() *EventBus {
return &EventBus{
ch: make(chan eventData, 100),
subscribers: map[Event][]func(any){
EventLabelMutation: {},
EventTagMutation: {},
EventLocationMutation: {},
EventItemMutation: {},
},

View File

@@ -15,7 +15,7 @@ type ExportItemFields struct {
type ExportCSVRow struct {
ImportRef string `csv:"HB.import_ref"`
Location LocationString `csv:"HB.location"`
LabelStr LabelString `csv:"HB.labels"`
TagStr TagString `csv:"HB.tags|HB.labels"`
AssetID repo.AssetID `csv:"HB.asset_id"`
Archived bool `csv:"HB.archived"`
URL string `csv:"HB.url"`
@@ -48,20 +48,20 @@ type ExportCSVRow struct {
// ============================================================================
// LabelString is a string slice that is used to represent a list of labels.
// TagString is a string slice that is used to represent a list of tags.
//
// For example, a list of labels "Important; Work" would be represented as a
// LabelString with the following values:
// For example, a list of tags "Important; Work" would be represented as a
// TagString with the following values:
//
// LabelString{"Important", "Work"}
type LabelString []string
// TagString{"Important", "Work"}
type TagString []string
func parseLabelString(s string) LabelString {
func parseTagString(s string) TagString {
v, _ := parseSeparatedString(s, ";")
return v
}
func (ls LabelString) String() string {
func (ls TagString) String() string {
return strings.Join(ls, "; ")
}

View File

@@ -41,6 +41,54 @@ func (s *IOSheet) indexHeaders() {
}
}
// primaryCSVTag returns the primary header name from a csv struct tag which
// may contain alternatives separated by '|'. For example: "HB.tags|HB.labels"
// will return "HB.tags".
func primaryCSVTag(tag string) string {
if tag == "" {
return ""
}
if i := strings.Index(tag, "|"); i >= 0 {
return strings.TrimSpace(tag[:i])
}
return tag
}
// csvTagAlternatives splits a tag with alternatives separated by '|'.
func csvTagAlternatives(tag string) []string {
if tag == "" {
return nil
}
parts := strings.Split(tag, "|")
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
return parts
}
// findColumnForTag tries to find a column index for a csv tag. The tag may
// contain multiple alternatives separated by '|'. It will return the first
// matching column index it finds.
func (s *IOSheet) findColumnForTag(tag string) (int, bool) {
if s.index == nil {
s.indexHeaders()
}
// Try primary tag first and then alternatives.
alts := csvTagAlternatives(tag)
if len(alts) == 0 {
return s.GetColumn(tag)
}
for _, t := range alts {
if col, ok := s.GetColumn(t); ok {
return col, true
}
}
return 0, false
}
func (s *IOSheet) GetColumn(str string) (col int, ok bool) {
if s.index == nil {
s.indexHeaders()
@@ -88,7 +136,7 @@ func (s *IOSheet) Read(data io.Reader) error {
continue
}
col, ok := s.GetColumn(tag)
col, ok := s.findColumnForTag(tag)
if !ok {
continue
}
@@ -114,8 +162,8 @@ func (s *IOSheet) Read(data io.Reader) error {
v, _ = repo.ParseAssetID(val)
case reflect.TypeOf(LocationString{}):
v = parseLocationString(val)
case reflect.TypeOf(LabelString{}):
v = parseLabelString(val)
case reflect.TypeOf(TagString{}):
v = parseTagString(val)
}
log.Debug().
@@ -172,10 +220,10 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, gid uuid.
locString := fromPathSlice(locPaths)
labelString := make([]string, len(item.Labels))
tagString := make([]string, len(item.Tags))
for i, l := range item.Labels {
labelString[i] = l.Name
for i, l := range item.Tags {
tagString[i] = l.Name
}
url := generateItemURL(item, hbURL)
@@ -194,7 +242,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, gid uuid.
s.Rows[i] = ExportCSVRow{
// fill struct
Location: locString,
LabelStr: labelString,
TagStr: tagString,
ImportRef: item.ImportRef,
AssetID: item.AssetID,
@@ -238,7 +286,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, gid uuid.
st := reflect.TypeOf(ExportCSVRow{})
// Write headers
// Write headers (use the primary tag when alternatives are provided)
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
tag := field.Tag.Get("csv")
@@ -246,7 +294,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, gid uuid.
continue
}
s.headers = append(s.headers, tag)
s.headers = append(s.headers, primaryCSVTag(tag))
}
for _, h := range customHeaders {
@@ -285,7 +333,7 @@ func (s *IOSheet) CSV() ([][]string, error) {
continue
}
col, ok := s.GetColumn(tag)
col, ok := s.findColumnForTag(tag)
if !ok {
continue
}
@@ -311,8 +359,8 @@ func (s *IOSheet) CSV() ([][]string, error) {
v = val.Interface().(repo.AssetID).String()
case reflect.TypeOf(LocationString{}):
v = val.Interface().(LocationString).String()
case reflect.TypeOf(LabelString{}):
v = val.Interface().(LabelString).String()
case reflect.TypeOf(TagString{}):
v = val.Interface().(TagString).String()
default:
log.Debug().Str("type", field.Type.String()).Msg("unknown type")
}

View File

@@ -77,19 +77,19 @@ func TestSheet_Read(t *testing.T) {
Name: "Item 1",
AssetID: repo.AssetID(1),
Location: LocationString{"Path", "To", "Location 1"},
LabelStr: LabelString{"L1", "L2", "L3"},
TagStr: TagString{"L1", "L2", "L3"},
},
{
Name: "Item 2",
AssetID: repo.AssetID(2),
Location: LocationString{"Path", "To", "Location 2"},
LabelStr: LabelString{"L1", "L2", "L3"},
TagStr: TagString{"L1", "L2", "L3"},
},
{
Name: "Item 3",
AssetID: repo.AssetID(1000003),
Location: LocationString{"Path", "To", "Location 3"},
LabelStr: LabelString{"L1", "L2", "L3"},
TagStr: TagString{"L1", "L2", "L3"},
},
},
},

View File

@@ -99,7 +99,7 @@ func serializeLocation[T ~[]string](location T) string {
//
// 1. If the item does not exist, it is created.
// 2. If the item has a ImportRef and it exists it is skipped
// 3. Locations and Labels are created if they do not exist.
// 3. Locations and Tags are created if they do not exist.
func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data io.Reader) (int, error) {
sheet := reporting.IOSheet{}
@@ -109,17 +109,17 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data io.Re
}
// ========================================
// Labels
// Tags
labelMap := make(map[string]uuid.UUID)
tagMap := make(map[string]uuid.UUID)
{
labels, err := svc.repo.Labels.GetAll(ctx, gid)
tags, err := svc.repo.Tags.GetAll(ctx, gid)
if err != nil {
return 0, err
}
for _, label := range labels {
labelMap[label.Name] = label.ID
for _, tag := range tags {
tagMap[tag.Name] = tag.ID
}
}
@@ -184,23 +184,23 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data io.Re
}
// ========================================
// Pre-Create Labels as necessary
labelIds := make([]uuid.UUID, len(row.LabelStr))
// Pre-Create tags as necessary
tagIds := make([]uuid.UUID, len(row.TagStr))
for j := range row.LabelStr {
label := row.LabelStr[j]
for j := range row.TagStr {
tag := row.TagStr[j]
id, ok := labelMap[label]
id, ok := tagMap[tag]
if !ok {
newLabel, err := svc.repo.Labels.Create(ctx, gid, repo.LabelCreate{Name: label})
newTag, err := svc.repo.Tags.Create(ctx, gid, repo.TagCreate{Name: tag})
if err != nil {
return 0, err
}
id = newLabel.ID
id = newTag.ID
}
labelIds[j] = id
labelMap[label] = id
tagIds[j] = id
tagMap[tag] = id
}
// ========================================
@@ -262,7 +262,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data io.Re
Description: row.Description,
AssetID: effAID,
LocationID: locationID,
LabelIDs: labelIds,
TagIDs: tagIds,
}
item, err = svc.repo.Items.Create(ctx, gid, newItem)
@@ -291,7 +291,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data io.Re
updateItem := repo.ItemUpdate{
ID: item.ID,
LabelIDs: labelIds,
TagIDs: tagIds,
LocationID: locationID,
Name: row.Name,

View File

@@ -43,7 +43,7 @@ type (
)
// RegisterUser creates a new user and group in the data with the provided data. It also bootstraps the user's group
// with default Labels and Locations.
// with default Tags and Locations.
func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration) (repo.UserOut, error) {
log.Debug().
Str("name", data.Name).
@@ -95,11 +95,11 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
}
log.Debug().Msg("user created")
// Create the default labels and locations for the group.
// Create the default tags and locations for the group.
if creatingGroup {
log.Debug().Msg("creating default labels")
for _, label := range defaultLabels() {
_, err := svc.repos.Labels.Create(ctx, usr.GroupID, label)
log.Debug().Msg("creating default tags")
for _, tag := range defaultTags() {
_, err := svc.repos.Tags.Create(ctx, usr.GroupID, tag)
if err != nil {
return repo.UserOut{}, err
}
@@ -300,11 +300,11 @@ func (svc *UserService) registerOIDCUser(ctx context.Context, issuer, subject, e
return repo.UserOut{}, err
}
log.Debug().Str("issuer", issuer).Str("subject", subject).Msg("creating default labels for OIDC user")
for _, label := range defaultLabels() {
_, err := svc.repos.Labels.Create(ctx, group.ID, label)
log.Debug().Str("issuer", issuer).Str("subject", subject).Msg("creating default tags for OIDC user")
for _, tag := range defaultTags() {
_, err := svc.repos.Tags.Create(ctx, group.ID, tag)
if err != nil {
log.Err(err).Msg("Failed to create default label")
log.Err(err).Msg("Failed to create default tag")
}
}

View File

@@ -33,8 +33,8 @@ func defaultLocations() []repo.LocationCreate {
}
}
func defaultLabels() []repo.LabelCreate {
return []repo.LabelCreate{
func defaultTags() []repo.TagCreate {
return []repo.TagCreate{
{
Name: "Appliances",
},

View File

@@ -24,10 +24,10 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemfield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/templatefield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
)
@@ -53,14 +53,14 @@ type Client struct {
ItemField *ItemFieldClient
// ItemTemplate is the client for interacting with the ItemTemplate builders.
ItemTemplate *ItemTemplateClient
// Label is the client for interacting with the Label builders.
Label *LabelClient
// Location is the client for interacting with the Location builders.
Location *LocationClient
// MaintenanceEntry is the client for interacting with the MaintenanceEntry builders.
MaintenanceEntry *MaintenanceEntryClient
// Notifier is the client for interacting with the Notifier builders.
Notifier *NotifierClient
// Tag is the client for interacting with the Tag builders.
Tag *TagClient
// TemplateField is the client for interacting with the TemplateField builders.
TemplateField *TemplateFieldClient
// User is the client for interacting with the User builders.
@@ -84,10 +84,10 @@ func (c *Client) init() {
c.Item = NewItemClient(c.config)
c.ItemField = NewItemFieldClient(c.config)
c.ItemTemplate = NewItemTemplateClient(c.config)
c.Label = NewLabelClient(c.config)
c.Location = NewLocationClient(c.config)
c.MaintenanceEntry = NewMaintenanceEntryClient(c.config)
c.Notifier = NewNotifierClient(c.config)
c.Tag = NewTagClient(c.config)
c.TemplateField = NewTemplateFieldClient(c.config)
c.User = NewUserClient(c.config)
}
@@ -190,10 +190,10 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
Item: NewItemClient(cfg),
ItemField: NewItemFieldClient(cfg),
ItemTemplate: NewItemTemplateClient(cfg),
Label: NewLabelClient(cfg),
Location: NewLocationClient(cfg),
MaintenanceEntry: NewMaintenanceEntryClient(cfg),
Notifier: NewNotifierClient(cfg),
Tag: NewTagClient(cfg),
TemplateField: NewTemplateFieldClient(cfg),
User: NewUserClient(cfg),
}, nil
@@ -223,10 +223,10 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
Item: NewItemClient(cfg),
ItemField: NewItemFieldClient(cfg),
ItemTemplate: NewItemTemplateClient(cfg),
Label: NewLabelClient(cfg),
Location: NewLocationClient(cfg),
MaintenanceEntry: NewMaintenanceEntryClient(cfg),
Notifier: NewNotifierClient(cfg),
Tag: NewTagClient(cfg),
TemplateField: NewTemplateFieldClient(cfg),
User: NewUserClient(cfg),
}, nil
@@ -259,8 +259,8 @@ func (c *Client) Close() error {
func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Group, c.GroupInvitationToken,
c.Item, c.ItemField, c.ItemTemplate, c.Label, c.Location, c.MaintenanceEntry,
c.Notifier, c.TemplateField, c.User,
c.Item, c.ItemField, c.ItemTemplate, c.Location, c.MaintenanceEntry,
c.Notifier, c.Tag, c.TemplateField, c.User,
} {
n.Use(hooks...)
}
@@ -271,8 +271,8 @@ func (c *Client) Use(hooks ...Hook) {
func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Group, c.GroupInvitationToken,
c.Item, c.ItemField, c.ItemTemplate, c.Label, c.Location, c.MaintenanceEntry,
c.Notifier, c.TemplateField, c.User,
c.Item, c.ItemField, c.ItemTemplate, c.Location, c.MaintenanceEntry,
c.Notifier, c.Tag, c.TemplateField, c.User,
} {
n.Intercept(interceptors...)
}
@@ -297,14 +297,14 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.ItemField.mutate(ctx, m)
case *ItemTemplateMutation:
return c.ItemTemplate.mutate(ctx, m)
case *LabelMutation:
return c.Label.mutate(ctx, m)
case *LocationMutation:
return c.Location.mutate(ctx, m)
case *MaintenanceEntryMutation:
return c.MaintenanceEntry.mutate(ctx, m)
case *NotifierMutation:
return c.Notifier.mutate(ctx, m)
case *TagMutation:
return c.Tag.mutate(ctx, m)
case *TemplateFieldMutation:
return c.TemplateField.mutate(ctx, m)
case *UserMutation:
@@ -949,15 +949,15 @@ func (c *GroupClient) QueryItems(_m *Group) *ItemQuery {
return query
}
// QueryLabels queries the labels edge of a Group.
func (c *GroupClient) QueryLabels(_m *Group) *LabelQuery {
query := (&LabelClient{config: c.config}).Query()
// QueryTags queries the tags edge of a Group.
func (c *GroupClient) QueryTags(_m *Group) *TagQuery {
query := (&TagClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(group.Table, group.FieldID, id),
sqlgraph.To(label.Table, label.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.LabelsTable, group.LabelsColumn),
sqlgraph.To(tag.Table, tag.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.TagsTable, group.TagsColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
@@ -1343,15 +1343,15 @@ func (c *ItemClient) QueryChildren(_m *Item) *ItemQuery {
return query
}
// QueryLabel queries the label edge of a Item.
func (c *ItemClient) QueryLabel(_m *Item) *LabelQuery {
query := (&LabelClient{config: c.config}).Query()
// QueryTag queries the tag edge of a Item.
func (c *ItemClient) QueryTag(_m *Item) *TagQuery {
query := (&TagClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(item.Table, item.FieldID, id),
sqlgraph.To(label.Table, label.FieldID),
sqlgraph.Edge(sqlgraph.M2M, true, item.LabelTable, item.LabelPrimaryKey...),
sqlgraph.To(tag.Table, tag.FieldID),
sqlgraph.Edge(sqlgraph.M2M, true, item.TagTable, item.TagPrimaryKey...),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
@@ -1778,171 +1778,6 @@ func (c *ItemTemplateClient) mutate(ctx context.Context, m *ItemTemplateMutation
}
}
// LabelClient is a client for the Label schema.
type LabelClient struct {
config
}
// NewLabelClient returns a client for the Label from the given config.
func NewLabelClient(c config) *LabelClient {
return &LabelClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `label.Hooks(f(g(h())))`.
func (c *LabelClient) Use(hooks ...Hook) {
c.hooks.Label = append(c.hooks.Label, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `label.Intercept(f(g(h())))`.
func (c *LabelClient) Intercept(interceptors ...Interceptor) {
c.inters.Label = append(c.inters.Label, interceptors...)
}
// Create returns a builder for creating a Label entity.
func (c *LabelClient) Create() *LabelCreate {
mutation := newLabelMutation(c.config, OpCreate)
return &LabelCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of Label entities.
func (c *LabelClient) CreateBulk(builders ...*LabelCreate) *LabelCreateBulk {
return &LabelCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *LabelClient) MapCreateBulk(slice any, setFunc func(*LabelCreate, int)) *LabelCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &LabelCreateBulk{err: fmt.Errorf("calling to LabelClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*LabelCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &LabelCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Label.
func (c *LabelClient) Update() *LabelUpdate {
mutation := newLabelMutation(c.config, OpUpdate)
return &LabelUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *LabelClient) UpdateOne(_m *Label) *LabelUpdateOne {
mutation := newLabelMutation(c.config, OpUpdateOne, withLabel(_m))
return &LabelUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *LabelClient) UpdateOneID(id uuid.UUID) *LabelUpdateOne {
mutation := newLabelMutation(c.config, OpUpdateOne, withLabelID(id))
return &LabelUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for Label.
func (c *LabelClient) Delete() *LabelDelete {
mutation := newLabelMutation(c.config, OpDelete)
return &LabelDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *LabelClient) DeleteOne(_m *Label) *LabelDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *LabelClient) DeleteOneID(id uuid.UUID) *LabelDeleteOne {
builder := c.Delete().Where(label.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &LabelDeleteOne{builder}
}
// Query returns a query builder for Label.
func (c *LabelClient) Query() *LabelQuery {
return &LabelQuery{
config: c.config,
ctx: &QueryContext{Type: TypeLabel},
inters: c.Interceptors(),
}
}
// Get returns a Label entity by its id.
func (c *LabelClient) Get(ctx context.Context, id uuid.UUID) (*Label, error) {
return c.Query().Where(label.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *LabelClient) GetX(ctx context.Context, id uuid.UUID) *Label {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryGroup queries the group edge of a Label.
func (c *LabelClient) QueryGroup(_m *Label) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(label.Table, label.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, label.GroupTable, label.GroupColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryItems queries the items edge of a Label.
func (c *LabelClient) QueryItems(_m *Label) *ItemQuery {
query := (&ItemClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(label.Table, label.FieldID, id),
sqlgraph.To(item.Table, item.FieldID),
sqlgraph.Edge(sqlgraph.M2M, false, label.ItemsTable, label.ItemsPrimaryKey...),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *LabelClient) Hooks() []Hook {
return c.hooks.Label
}
// Interceptors returns the client interceptors.
func (c *LabelClient) Interceptors() []Interceptor {
return c.inters.Label
}
func (c *LabelClient) mutate(ctx context.Context, m *LabelMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&LabelCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&LabelUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&LabelUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&LabelDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown Label mutation op: %q", m.Op())
}
}
// LocationClient is a client for the Location schema.
type LocationClient struct {
config
@@ -2454,6 +2289,171 @@ func (c *NotifierClient) mutate(ctx context.Context, m *NotifierMutation) (Value
}
}
// TagClient is a client for the Tag schema.
type TagClient struct {
config
}
// NewTagClient returns a client for the Tag from the given config.
func NewTagClient(c config) *TagClient {
return &TagClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `tag.Hooks(f(g(h())))`.
func (c *TagClient) Use(hooks ...Hook) {
c.hooks.Tag = append(c.hooks.Tag, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `tag.Intercept(f(g(h())))`.
func (c *TagClient) Intercept(interceptors ...Interceptor) {
c.inters.Tag = append(c.inters.Tag, interceptors...)
}
// Create returns a builder for creating a Tag entity.
func (c *TagClient) Create() *TagCreate {
mutation := newTagMutation(c.config, OpCreate)
return &TagCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of Tag entities.
func (c *TagClient) CreateBulk(builders ...*TagCreate) *TagCreateBulk {
return &TagCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *TagClient) MapCreateBulk(slice any, setFunc func(*TagCreate, int)) *TagCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &TagCreateBulk{err: fmt.Errorf("calling to TagClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*TagCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &TagCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Tag.
func (c *TagClient) Update() *TagUpdate {
mutation := newTagMutation(c.config, OpUpdate)
return &TagUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *TagClient) UpdateOne(_m *Tag) *TagUpdateOne {
mutation := newTagMutation(c.config, OpUpdateOne, withTag(_m))
return &TagUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *TagClient) UpdateOneID(id uuid.UUID) *TagUpdateOne {
mutation := newTagMutation(c.config, OpUpdateOne, withTagID(id))
return &TagUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for Tag.
func (c *TagClient) Delete() *TagDelete {
mutation := newTagMutation(c.config, OpDelete)
return &TagDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *TagClient) DeleteOne(_m *Tag) *TagDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *TagClient) DeleteOneID(id uuid.UUID) *TagDeleteOne {
builder := c.Delete().Where(tag.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &TagDeleteOne{builder}
}
// Query returns a query builder for Tag.
func (c *TagClient) Query() *TagQuery {
return &TagQuery{
config: c.config,
ctx: &QueryContext{Type: TypeTag},
inters: c.Interceptors(),
}
}
// Get returns a Tag entity by its id.
func (c *TagClient) Get(ctx context.Context, id uuid.UUID) (*Tag, error) {
return c.Query().Where(tag.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *TagClient) GetX(ctx context.Context, id uuid.UUID) *Tag {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryGroup queries the group edge of a Tag.
func (c *TagClient) QueryGroup(_m *Tag) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(tag.Table, tag.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, tag.GroupTable, tag.GroupColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryItems queries the items edge of a Tag.
func (c *TagClient) QueryItems(_m *Tag) *ItemQuery {
query := (&ItemClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(tag.Table, tag.FieldID, id),
sqlgraph.To(item.Table, item.FieldID),
sqlgraph.Edge(sqlgraph.M2M, false, tag.ItemsTable, tag.ItemsPrimaryKey...),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *TagClient) Hooks() []Hook {
return c.hooks.Tag
}
// Interceptors returns the client interceptors.
func (c *TagClient) Interceptors() []Interceptor {
return c.inters.Tag
}
func (c *TagClient) mutate(ctx context.Context, m *TagMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&TagCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&TagUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&TagUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&TagDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown Tag mutation op: %q", m.Op())
}
}
// TemplateFieldClient is a client for the TemplateField schema.
type TemplateFieldClient struct {
config
@@ -2788,12 +2788,12 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error)
type (
hooks struct {
Attachment, AuthRoles, AuthTokens, Group, GroupInvitationToken, Item, ItemField,
ItemTemplate, Label, Location, MaintenanceEntry, Notifier, TemplateField,
ItemTemplate, Location, MaintenanceEntry, Notifier, Tag, TemplateField,
User []ent.Hook
}
inters struct {
Attachment, AuthRoles, AuthTokens, Group, GroupInvitationToken, Item, ItemField,
ItemTemplate, Label, Location, MaintenanceEntry, Notifier, TemplateField,
ItemTemplate, Location, MaintenanceEntry, Notifier, Tag, TemplateField,
User []ent.Interceptor
}
)

View File

@@ -20,10 +20,10 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemfield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/templatefield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
)
@@ -94,10 +94,10 @@ func checkColumn(t, c string) error {
item.Table: item.ValidColumn,
itemfield.Table: itemfield.ValidColumn,
itemtemplate.Table: itemtemplate.ValidColumn,
label.Table: label.ValidColumn,
location.Table: location.ValidColumn,
maintenanceentry.Table: maintenanceentry.ValidColumn,
notifier.Table: notifier.ValidColumn,
tag.Table: tag.ValidColumn,
templatefield.Table: templatefield.ValidColumn,
user.Table: user.ValidColumn,
})

View File

@@ -40,8 +40,8 @@ type GroupEdges struct {
Locations []*Location `json:"locations,omitempty"`
// Items holds the value of the items edge.
Items []*Item `json:"items,omitempty"`
// Labels holds the value of the labels edge.
Labels []*Label `json:"labels,omitempty"`
// Tags holds the value of the tags edge.
Tags []*Tag `json:"tags,omitempty"`
// InvitationTokens holds the value of the invitation_tokens edge.
InvitationTokens []*GroupInvitationToken `json:"invitation_tokens,omitempty"`
// Notifiers holds the value of the notifiers edge.
@@ -80,13 +80,13 @@ func (e GroupEdges) ItemsOrErr() ([]*Item, error) {
return nil, &NotLoadedError{edge: "items"}
}
// LabelsOrErr returns the Labels value or an error if the edge
// TagsOrErr returns the Tags value or an error if the edge
// was not loaded in eager-loading.
func (e GroupEdges) LabelsOrErr() ([]*Label, error) {
func (e GroupEdges) TagsOrErr() ([]*Tag, error) {
if e.loadedTypes[3] {
return e.Labels, nil
return e.Tags, nil
}
return nil, &NotLoadedError{edge: "labels"}
return nil, &NotLoadedError{edge: "tags"}
}
// InvitationTokensOrErr returns the InvitationTokens value or an error if the edge
@@ -200,9 +200,9 @@ func (_m *Group) QueryItems() *ItemQuery {
return NewGroupClient(_m.config).QueryItems(_m)
}
// QueryLabels queries the "labels" edge of the Group entity.
func (_m *Group) QueryLabels() *LabelQuery {
return NewGroupClient(_m.config).QueryLabels(_m)
// QueryTags queries the "tags" edge of the Group entity.
func (_m *Group) QueryTags() *TagQuery {
return NewGroupClient(_m.config).QueryTags(_m)
}
// QueryInvitationTokens queries the "invitation_tokens" edge of the Group entity.

View File

@@ -29,8 +29,8 @@ const (
EdgeLocations = "locations"
// EdgeItems holds the string denoting the items edge name in mutations.
EdgeItems = "items"
// EdgeLabels holds the string denoting the labels edge name in mutations.
EdgeLabels = "labels"
// EdgeTags holds the string denoting the tags edge name in mutations.
EdgeTags = "tags"
// EdgeInvitationTokens holds the string denoting the invitation_tokens edge name in mutations.
EdgeInvitationTokens = "invitation_tokens"
// EdgeNotifiers holds the string denoting the notifiers edge name in mutations.
@@ -60,13 +60,13 @@ const (
ItemsInverseTable = "items"
// ItemsColumn is the table column denoting the items relation/edge.
ItemsColumn = "group_items"
// LabelsTable is the table that holds the labels relation/edge.
LabelsTable = "labels"
// LabelsInverseTable is the table name for the Label entity.
// It exists in this package in order to avoid circular dependency with the "label" package.
LabelsInverseTable = "labels"
// LabelsColumn is the table column denoting the labels relation/edge.
LabelsColumn = "group_labels"
// TagsTable is the table that holds the tags relation/edge.
TagsTable = "tags"
// TagsInverseTable is the table name for the Tag entity.
// It exists in this package in order to avoid circular dependency with the "tag" package.
TagsInverseTable = "tags"
// TagsColumn is the table column denoting the tags relation/edge.
TagsColumn = "group_tags"
// InvitationTokensTable is the table that holds the invitation_tokens relation/edge.
InvitationTokensTable = "group_invitation_tokens"
// InvitationTokensInverseTable is the table name for the GroupInvitationToken entity.
@@ -194,17 +194,17 @@ func ByItems(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
}
}
// ByLabelsCount orders the results by labels count.
func ByLabelsCount(opts ...sql.OrderTermOption) OrderOption {
// ByTagsCount orders the results by tags count.
func ByTagsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newLabelsStep(), opts...)
sqlgraph.OrderByNeighborsCount(s, newTagsStep(), opts...)
}
}
// ByLabels orders the results by labels terms.
func ByLabels(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
// ByTags orders the results by tags terms.
func ByTags(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newLabelsStep(), append([]sql.OrderTerm{term}, terms...)...)
sqlgraph.OrderByNeighborTerms(s, newTagsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
@@ -270,11 +270,11 @@ func newItemsStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, ItemsTable, ItemsColumn),
)
}
func newLabelsStep() *sqlgraph.Step {
func newTagsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(LabelsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, LabelsTable, LabelsColumn),
sqlgraph.To(TagsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, TagsTable, TagsColumn),
)
}
func newInvitationTokensStep() *sqlgraph.Step {

View File

@@ -355,21 +355,21 @@ func HasItemsWith(preds ...predicate.Item) predicate.Group {
})
}
// HasLabels applies the HasEdge predicate on the "labels" edge.
func HasLabels() predicate.Group {
// HasTags applies the HasEdge predicate on the "tags" edge.
func HasTags() predicate.Group {
return predicate.Group(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, LabelsTable, LabelsColumn),
sqlgraph.Edge(sqlgraph.O2M, false, TagsTable, TagsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasLabelsWith applies the HasEdge predicate on the "labels" edge with a given conditions (other predicates).
func HasLabelsWith(preds ...predicate.Label) predicate.Group {
// HasTagsWith applies the HasEdge predicate on the "tags" edge with a given conditions (other predicates).
func HasTagsWith(preds ...predicate.Tag) predicate.Group {
return predicate.Group(func(s *sql.Selector) {
step := newLabelsStep()
step := newTagsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)

View File

@@ -15,9 +15,9 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
)
@@ -135,19 +135,19 @@ func (_c *GroupCreate) AddItems(v ...*Item) *GroupCreate {
return _c.AddItemIDs(ids...)
}
// AddLabelIDs adds the "labels" edge to the Label entity by IDs.
func (_c *GroupCreate) AddLabelIDs(ids ...uuid.UUID) *GroupCreate {
_c.mutation.AddLabelIDs(ids...)
// AddTagIDs adds the "tags" edge to the Tag entity by IDs.
func (_c *GroupCreate) AddTagIDs(ids ...uuid.UUID) *GroupCreate {
_c.mutation.AddTagIDs(ids...)
return _c
}
// AddLabels adds the "labels" edges to the Label entity.
func (_c *GroupCreate) AddLabels(v ...*Label) *GroupCreate {
// AddTags adds the "tags" edges to the Tag entity.
func (_c *GroupCreate) AddTags(v ...*Tag) *GroupCreate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddLabelIDs(ids...)
return _c.AddTagIDs(ids...)
}
// AddInvitationTokenIDs adds the "invitation_tokens" edge to the GroupInvitationToken entity by IDs.
@@ -366,15 +366,15 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.LabelsIDs(); len(nodes) > 0 {
if nodes := _c.mutation.TagsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.LabelsTable,
Columns: []string{group.LabelsColumn},
Table: group.TagsTable,
Columns: []string{group.TagsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -17,10 +17,10 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
)
@@ -34,7 +34,7 @@ type GroupQuery struct {
withUsers *UserQuery
withLocations *LocationQuery
withItems *ItemQuery
withLabels *LabelQuery
withTags *TagQuery
withInvitationTokens *GroupInvitationTokenQuery
withNotifiers *NotifierQuery
withItemTemplates *ItemTemplateQuery
@@ -140,9 +140,9 @@ func (_q *GroupQuery) QueryItems() *ItemQuery {
return query
}
// QueryLabels chains the current query on the "labels" edge.
func (_q *GroupQuery) QueryLabels() *LabelQuery {
query := (&LabelClient{config: _q.config}).Query()
// QueryTags chains the current query on the "tags" edge.
func (_q *GroupQuery) QueryTags() *TagQuery {
query := (&TagClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
@@ -153,8 +153,8 @@ func (_q *GroupQuery) QueryLabels() *LabelQuery {
}
step := sqlgraph.NewStep(
sqlgraph.From(group.Table, group.FieldID, selector),
sqlgraph.To(label.Table, label.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.LabelsTable, group.LabelsColumn),
sqlgraph.To(tag.Table, tag.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.TagsTable, group.TagsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
@@ -423,7 +423,7 @@ func (_q *GroupQuery) Clone() *GroupQuery {
withUsers: _q.withUsers.Clone(),
withLocations: _q.withLocations.Clone(),
withItems: _q.withItems.Clone(),
withLabels: _q.withLabels.Clone(),
withTags: _q.withTags.Clone(),
withInvitationTokens: _q.withInvitationTokens.Clone(),
withNotifiers: _q.withNotifiers.Clone(),
withItemTemplates: _q.withItemTemplates.Clone(),
@@ -466,14 +466,14 @@ func (_q *GroupQuery) WithItems(opts ...func(*ItemQuery)) *GroupQuery {
return _q
}
// WithLabels tells the query-builder to eager-load the nodes that are connected to
// the "labels" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *GroupQuery) WithLabels(opts ...func(*LabelQuery)) *GroupQuery {
query := (&LabelClient{config: _q.config}).Query()
// WithTags tells the query-builder to eager-load the nodes that are connected to
// the "tags" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *GroupQuery) WithTags(opts ...func(*TagQuery)) *GroupQuery {
query := (&TagClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withLabels = query
_q.withTags = query
return _q
}
@@ -592,7 +592,7 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
_q.withUsers != nil,
_q.withLocations != nil,
_q.withItems != nil,
_q.withLabels != nil,
_q.withTags != nil,
_q.withInvitationTokens != nil,
_q.withNotifiers != nil,
_q.withItemTemplates != nil,
@@ -637,10 +637,10 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
return nil, err
}
}
if query := _q.withLabels; query != nil {
if err := _q.loadLabels(ctx, query, nodes,
func(n *Group) { n.Edges.Labels = []*Label{} },
func(n *Group, e *Label) { n.Edges.Labels = append(n.Edges.Labels, e) }); err != nil {
if query := _q.withTags; query != nil {
if err := _q.loadTags(ctx, query, nodes,
func(n *Group) { n.Edges.Tags = []*Tag{} },
func(n *Group, e *Tag) { n.Edges.Tags = append(n.Edges.Tags, e) }); err != nil {
return nil, err
}
}
@@ -763,7 +763,7 @@ func (_q *GroupQuery) loadItems(ctx context.Context, query *ItemQuery, nodes []*
}
return nil
}
func (_q *GroupQuery) loadLabels(ctx context.Context, query *LabelQuery, nodes []*Group, init func(*Group), assign func(*Group, *Label)) error {
func (_q *GroupQuery) loadTags(ctx context.Context, query *TagQuery, nodes []*Group, init func(*Group), assign func(*Group, *Tag)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*Group)
for i := range nodes {
@@ -774,21 +774,21 @@ func (_q *GroupQuery) loadLabels(ctx context.Context, query *LabelQuery, nodes [
}
}
query.withFKs = true
query.Where(predicate.Label(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(group.LabelsColumn), fks...))
query.Where(predicate.Tag(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(group.TagsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.group_labels
fk := n.group_tags
if fk == nil {
return fmt.Errorf(`foreign-key "group_labels" is nil for node %v`, n.ID)
return fmt.Errorf(`foreign-key "group_tags" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "group_labels" returned %v for node %v`, *fk, n.ID)
return fmt.Errorf(`unexpected referenced foreign-key "group_tags" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}

View File

@@ -16,10 +16,10 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
)
@@ -115,19 +115,19 @@ func (_u *GroupUpdate) AddItems(v ...*Item) *GroupUpdate {
return _u.AddItemIDs(ids...)
}
// AddLabelIDs adds the "labels" edge to the Label entity by IDs.
func (_u *GroupUpdate) AddLabelIDs(ids ...uuid.UUID) *GroupUpdate {
_u.mutation.AddLabelIDs(ids...)
// AddTagIDs adds the "tags" edge to the Tag entity by IDs.
func (_u *GroupUpdate) AddTagIDs(ids ...uuid.UUID) *GroupUpdate {
_u.mutation.AddTagIDs(ids...)
return _u
}
// AddLabels adds the "labels" edges to the Label entity.
func (_u *GroupUpdate) AddLabels(v ...*Label) *GroupUpdate {
// AddTags adds the "tags" edges to the Tag entity.
func (_u *GroupUpdate) AddTags(v ...*Tag) *GroupUpdate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddLabelIDs(ids...)
return _u.AddTagIDs(ids...)
}
// AddInvitationTokenIDs adds the "invitation_tokens" edge to the GroupInvitationToken entity by IDs.
@@ -243,25 +243,25 @@ func (_u *GroupUpdate) RemoveItems(v ...*Item) *GroupUpdate {
return _u.RemoveItemIDs(ids...)
}
// ClearLabels clears all "labels" edges to the Label entity.
func (_u *GroupUpdate) ClearLabels() *GroupUpdate {
_u.mutation.ClearLabels()
// ClearTags clears all "tags" edges to the Tag entity.
func (_u *GroupUpdate) ClearTags() *GroupUpdate {
_u.mutation.ClearTags()
return _u
}
// RemoveLabelIDs removes the "labels" edge to Label entities by IDs.
func (_u *GroupUpdate) RemoveLabelIDs(ids ...uuid.UUID) *GroupUpdate {
_u.mutation.RemoveLabelIDs(ids...)
// RemoveTagIDs removes the "tags" edge to Tag entities by IDs.
func (_u *GroupUpdate) RemoveTagIDs(ids ...uuid.UUID) *GroupUpdate {
_u.mutation.RemoveTagIDs(ids...)
return _u
}
// RemoveLabels removes "labels" edges to Label entities.
func (_u *GroupUpdate) RemoveLabels(v ...*Label) *GroupUpdate {
// RemoveTags removes "tags" edges to Tag entities.
func (_u *GroupUpdate) RemoveTags(v ...*Tag) *GroupUpdate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveLabelIDs(ids...)
return _u.RemoveTagIDs(ids...)
}
// ClearInvitationTokens clears all "invitation_tokens" edges to the GroupInvitationToken entity.
@@ -529,28 +529,28 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.LabelsCleared() {
if _u.mutation.TagsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.LabelsTable,
Columns: []string{group.LabelsColumn},
Table: group.TagsTable,
Columns: []string{group.TagsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedLabelsIDs(); len(nodes) > 0 && !_u.mutation.LabelsCleared() {
if nodes := _u.mutation.RemovedTagsIDs(); len(nodes) > 0 && !_u.mutation.TagsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.LabelsTable,
Columns: []string{group.LabelsColumn},
Table: group.TagsTable,
Columns: []string{group.TagsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -558,15 +558,15 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.LabelsIDs(); len(nodes) > 0 {
if nodes := _u.mutation.TagsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.LabelsTable,
Columns: []string{group.LabelsColumn},
Table: group.TagsTable,
Columns: []string{group.TagsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -808,19 +808,19 @@ func (_u *GroupUpdateOne) AddItems(v ...*Item) *GroupUpdateOne {
return _u.AddItemIDs(ids...)
}
// AddLabelIDs adds the "labels" edge to the Label entity by IDs.
func (_u *GroupUpdateOne) AddLabelIDs(ids ...uuid.UUID) *GroupUpdateOne {
_u.mutation.AddLabelIDs(ids...)
// AddTagIDs adds the "tags" edge to the Tag entity by IDs.
func (_u *GroupUpdateOne) AddTagIDs(ids ...uuid.UUID) *GroupUpdateOne {
_u.mutation.AddTagIDs(ids...)
return _u
}
// AddLabels adds the "labels" edges to the Label entity.
func (_u *GroupUpdateOne) AddLabels(v ...*Label) *GroupUpdateOne {
// AddTags adds the "tags" edges to the Tag entity.
func (_u *GroupUpdateOne) AddTags(v ...*Tag) *GroupUpdateOne {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddLabelIDs(ids...)
return _u.AddTagIDs(ids...)
}
// AddInvitationTokenIDs adds the "invitation_tokens" edge to the GroupInvitationToken entity by IDs.
@@ -936,25 +936,25 @@ func (_u *GroupUpdateOne) RemoveItems(v ...*Item) *GroupUpdateOne {
return _u.RemoveItemIDs(ids...)
}
// ClearLabels clears all "labels" edges to the Label entity.
func (_u *GroupUpdateOne) ClearLabels() *GroupUpdateOne {
_u.mutation.ClearLabels()
// ClearTags clears all "tags" edges to the Tag entity.
func (_u *GroupUpdateOne) ClearTags() *GroupUpdateOne {
_u.mutation.ClearTags()
return _u
}
// RemoveLabelIDs removes the "labels" edge to Label entities by IDs.
func (_u *GroupUpdateOne) RemoveLabelIDs(ids ...uuid.UUID) *GroupUpdateOne {
_u.mutation.RemoveLabelIDs(ids...)
// RemoveTagIDs removes the "tags" edge to Tag entities by IDs.
func (_u *GroupUpdateOne) RemoveTagIDs(ids ...uuid.UUID) *GroupUpdateOne {
_u.mutation.RemoveTagIDs(ids...)
return _u
}
// RemoveLabels removes "labels" edges to Label entities.
func (_u *GroupUpdateOne) RemoveLabels(v ...*Label) *GroupUpdateOne {
// RemoveTags removes "tags" edges to Tag entities.
func (_u *GroupUpdateOne) RemoveTags(v ...*Tag) *GroupUpdateOne {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveLabelIDs(ids...)
return _u.RemoveTagIDs(ids...)
}
// ClearInvitationTokens clears all "invitation_tokens" edges to the GroupInvitationToken entity.
@@ -1252,28 +1252,28 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.LabelsCleared() {
if _u.mutation.TagsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.LabelsTable,
Columns: []string{group.LabelsColumn},
Table: group.TagsTable,
Columns: []string{group.TagsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedLabelsIDs(); len(nodes) > 0 && !_u.mutation.LabelsCleared() {
if nodes := _u.mutation.RemovedTagsIDs(); len(nodes) > 0 && !_u.mutation.TagsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.LabelsTable,
Columns: []string{group.LabelsColumn},
Table: group.TagsTable,
Columns: []string{group.TagsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1281,15 +1281,15 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.LabelsIDs(); len(nodes) > 0 {
if nodes := _u.mutation.TagsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.LabelsTable,
Columns: []string{group.LabelsColumn},
Table: group.TagsTable,
Columns: []string{group.TagsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -36,10 +36,6 @@ func (_m *ItemTemplate) GetID() uuid.UUID {
return _m.ID
}
func (_m *Label) GetID() uuid.UUID {
return _m.ID
}
func (_m *Location) GetID() uuid.UUID {
return _m.ID
}
@@ -52,6 +48,10 @@ func (_m *Notifier) GetID() uuid.UUID {
return _m.ID
}
func (_m *Tag) GetID() uuid.UUID {
return _m.ID
}
func (_m *TemplateField) GetID() uuid.UUID {
return _m.ID
}

View File

@@ -105,18 +105,6 @@ func (f ItemTemplateFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ItemTemplateMutation", m)
}
// The LabelFunc type is an adapter to allow the use of ordinary
// function as Label mutator.
type LabelFunc func(context.Context, *ent.LabelMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f LabelFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.LabelMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.LabelMutation", m)
}
// The LocationFunc type is an adapter to allow the use of ordinary
// function as Location mutator.
type LocationFunc func(context.Context, *ent.LocationMutation) (ent.Value, error)
@@ -153,6 +141,18 @@ func (f NotifierFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, er
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.NotifierMutation", m)
}
// The TagFunc type is an adapter to allow the use of ordinary
// function as Tag mutator.
type TagFunc func(context.Context, *ent.TagMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f TagFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.TagMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.TagMutation", m)
}
// The TemplateFieldFunc type is an adapter to allow the use of ordinary
// function as TemplateField mutator.
type TemplateFieldFunc func(context.Context, *ent.TemplateFieldMutation) (ent.Value, error)

View File

@@ -85,8 +85,8 @@ type ItemEdges struct {
Parent *Item `json:"parent,omitempty"`
// Children holds the value of the children edge.
Children []*Item `json:"children,omitempty"`
// Label holds the value of the label edge.
Label []*Label `json:"label,omitempty"`
// Tag holds the value of the tag edge.
Tag []*Tag `json:"tag,omitempty"`
// Location holds the value of the location edge.
Location *Location `json:"location,omitempty"`
// Fields holds the value of the fields edge.
@@ -131,13 +131,13 @@ func (e ItemEdges) ChildrenOrErr() ([]*Item, error) {
return nil, &NotLoadedError{edge: "children"}
}
// LabelOrErr returns the Label value or an error if the edge
// TagOrErr returns the Tag value or an error if the edge
// was not loaded in eager-loading.
func (e ItemEdges) LabelOrErr() ([]*Label, error) {
func (e ItemEdges) TagOrErr() ([]*Tag, error) {
if e.loadedTypes[3] {
return e.Label, nil
return e.Tag, nil
}
return nil, &NotLoadedError{edge: "label"}
return nil, &NotLoadedError{edge: "tag"}
}
// LocationOrErr returns the Location value or an error if the edge
@@ -415,9 +415,9 @@ func (_m *Item) QueryChildren() *ItemQuery {
return NewItemClient(_m.config).QueryChildren(_m)
}
// QueryLabel queries the "label" edge of the Item entity.
func (_m *Item) QueryLabel() *LabelQuery {
return NewItemClient(_m.config).QueryLabel(_m)
// QueryTag queries the "tag" edge of the Item entity.
func (_m *Item) QueryTag() *TagQuery {
return NewItemClient(_m.config).QueryTag(_m)
}
// QueryLocation queries the "location" edge of the Item entity.

View File

@@ -69,8 +69,8 @@ const (
EdgeParent = "parent"
// EdgeChildren holds the string denoting the children edge name in mutations.
EdgeChildren = "children"
// EdgeLabel holds the string denoting the label edge name in mutations.
EdgeLabel = "label"
// EdgeTag holds the string denoting the tag edge name in mutations.
EdgeTag = "tag"
// EdgeLocation holds the string denoting the location edge name in mutations.
EdgeLocation = "location"
// EdgeFields holds the string denoting the fields edge name in mutations.
@@ -96,11 +96,11 @@ const (
ChildrenTable = "items"
// ChildrenColumn is the table column denoting the children relation/edge.
ChildrenColumn = "item_children"
// LabelTable is the table that holds the label relation/edge. The primary key declared below.
LabelTable = "label_items"
// LabelInverseTable is the table name for the Label entity.
// It exists in this package in order to avoid circular dependency with the "label" package.
LabelInverseTable = "labels"
// TagTable is the table that holds the tag relation/edge. The primary key declared below.
TagTable = "tag_items"
// TagInverseTable is the table name for the Tag entity.
// It exists in this package in order to avoid circular dependency with the "tag" package.
TagInverseTable = "tags"
// LocationTable is the table that holds the location relation/edge.
LocationTable = "items"
// LocationInverseTable is the table name for the Location entity.
@@ -169,9 +169,9 @@ var ForeignKeys = []string{
}
var (
// LabelPrimaryKey and LabelColumn2 are the table columns denoting the
// primary key for the label relation (M2M).
LabelPrimaryKey = []string{"label_id", "item_id"}
// TagPrimaryKey and TagColumn2 are the table columns denoting the
// primary key for the tag relation (M2M).
TagPrimaryKey = []string{"tag_id", "item_id"}
)
// ValidColumn reports if the column name is valid (part of the table columns).
@@ -390,17 +390,17 @@ func ByChildren(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
}
}
// ByLabelCount orders the results by label count.
func ByLabelCount(opts ...sql.OrderTermOption) OrderOption {
// ByTagCount orders the results by tag count.
func ByTagCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newLabelStep(), opts...)
sqlgraph.OrderByNeighborsCount(s, newTagStep(), opts...)
}
}
// ByLabel orders the results by label terms.
func ByLabel(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
// ByTag orders the results by tag terms.
func ByTag(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newLabelStep(), append([]sql.OrderTerm{term}, terms...)...)
sqlgraph.OrderByNeighborTerms(s, newTagStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
@@ -473,11 +473,11 @@ func newChildrenStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn),
)
}
func newLabelStep() *sqlgraph.Step {
func newTagStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(LabelInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2M, true, LabelTable, LabelPrimaryKey...),
sqlgraph.To(TagInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2M, true, TagTable, TagPrimaryKey...),
)
}
func newLocationStep() *sqlgraph.Step {

View File

@@ -1490,21 +1490,21 @@ func HasChildrenWith(preds ...predicate.Item) predicate.Item {
})
}
// HasLabel applies the HasEdge predicate on the "label" edge.
func HasLabel() predicate.Item {
// HasTag applies the HasEdge predicate on the "tag" edge.
func HasTag() predicate.Item {
return predicate.Item(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2M, true, LabelTable, LabelPrimaryKey...),
sqlgraph.Edge(sqlgraph.M2M, true, TagTable, TagPrimaryKey...),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasLabelWith applies the HasEdge predicate on the "label" edge with a given conditions (other predicates).
func HasLabelWith(preds ...predicate.Label) predicate.Item {
// HasTagWith applies the HasEdge predicate on the "tag" edge with a given conditions (other predicates).
func HasTagWith(preds ...predicate.Tag) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
step := newLabelStep()
step := newTagStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)

View File

@@ -15,9 +15,9 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemfield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// ItemCreate is the builder for creating a Item entity.
@@ -414,19 +414,19 @@ func (_c *ItemCreate) AddChildren(v ...*Item) *ItemCreate {
return _c.AddChildIDs(ids...)
}
// AddLabelIDs adds the "label" edge to the Label entity by IDs.
func (_c *ItemCreate) AddLabelIDs(ids ...uuid.UUID) *ItemCreate {
_c.mutation.AddLabelIDs(ids...)
// AddTagIDs adds the "tag" edge to the Tag entity by IDs.
func (_c *ItemCreate) AddTagIDs(ids ...uuid.UUID) *ItemCreate {
_c.mutation.AddTagIDs(ids...)
return _c
}
// AddLabel adds the "label" edges to the Label entity.
func (_c *ItemCreate) AddLabel(v ...*Label) *ItemCreate {
// AddTag adds the "tag" edges to the Tag entity.
func (_c *ItemCreate) AddTag(v ...*Tag) *ItemCreate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddLabelIDs(ids...)
return _c.AddTagIDs(ids...)
}
// SetLocationID sets the "location" edge to the Location entity by ID.
@@ -838,15 +838,15 @@ func (_c *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.LabelIDs(); len(nodes) > 0 {
if nodes := _c.mutation.TagIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: true,
Table: item.LabelTable,
Columns: item.LabelPrimaryKey,
Table: item.TagTable,
Columns: item.TagPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -4,6 +4,7 @@ import (
"entgo.io/ent/dialect/sql"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
conf "github.com/sysadminsmedia/homebox/backend/internal/sys/config"
"github.com/sysadminsmedia/homebox/backend/pkgs/textutils"
)
@@ -24,7 +25,7 @@ func AccentInsensitiveContains(field string, searchValue string) predicate.Item
dialect := s.Dialect()
switch dialect {
case "sqlite3":
case conf.DriverSqlite3:
// For SQLite, we'll create a custom normalization function using REPLACE
// to handle common accented characters
normalizeFunc := buildSQLiteNormalizeExpression(s.C(field))
@@ -32,7 +33,7 @@ func AccentInsensitiveContains(field string, searchValue string) predicate.Item
"LOWER("+normalizeFunc+") LIKE ?",
"%"+normalizedSearch+"%",
))
case "postgres":
case conf.DriverPostgres:
// For PostgreSQL, use REPLACE-based normalization to avoid unaccent dependency
normalizeFunc := buildGenericNormalizeExpression(s.C(field))
// Use sql.P() for proper PostgreSQL parameter binding ($1, $2, etc.)

View File

@@ -17,10 +17,10 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemfield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// ItemQuery is the builder for querying Item entities.
@@ -33,7 +33,7 @@ type ItemQuery struct {
withGroup *GroupQuery
withParent *ItemQuery
withChildren *ItemQuery
withLabel *LabelQuery
withTag *TagQuery
withLocation *LocationQuery
withFields *ItemFieldQuery
withMaintenanceEntries *MaintenanceEntryQuery
@@ -141,9 +141,9 @@ func (_q *ItemQuery) QueryChildren() *ItemQuery {
return query
}
// QueryLabel chains the current query on the "label" edge.
func (_q *ItemQuery) QueryLabel() *LabelQuery {
query := (&LabelClient{config: _q.config}).Query()
// QueryTag chains the current query on the "tag" edge.
func (_q *ItemQuery) QueryTag() *TagQuery {
query := (&TagClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
@@ -154,8 +154,8 @@ func (_q *ItemQuery) QueryLabel() *LabelQuery {
}
step := sqlgraph.NewStep(
sqlgraph.From(item.Table, item.FieldID, selector),
sqlgraph.To(label.Table, label.FieldID),
sqlgraph.Edge(sqlgraph.M2M, true, item.LabelTable, item.LabelPrimaryKey...),
sqlgraph.To(tag.Table, tag.FieldID),
sqlgraph.Edge(sqlgraph.M2M, true, item.TagTable, item.TagPrimaryKey...),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
@@ -446,7 +446,7 @@ func (_q *ItemQuery) Clone() *ItemQuery {
withGroup: _q.withGroup.Clone(),
withParent: _q.withParent.Clone(),
withChildren: _q.withChildren.Clone(),
withLabel: _q.withLabel.Clone(),
withTag: _q.withTag.Clone(),
withLocation: _q.withLocation.Clone(),
withFields: _q.withFields.Clone(),
withMaintenanceEntries: _q.withMaintenanceEntries.Clone(),
@@ -490,14 +490,14 @@ func (_q *ItemQuery) WithChildren(opts ...func(*ItemQuery)) *ItemQuery {
return _q
}
// WithLabel tells the query-builder to eager-load the nodes that are connected to
// the "label" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ItemQuery) WithLabel(opts ...func(*LabelQuery)) *ItemQuery {
query := (&LabelClient{config: _q.config}).Query()
// WithTag tells the query-builder to eager-load the nodes that are connected to
// the "tag" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ItemQuery) WithTag(opts ...func(*TagQuery)) *ItemQuery {
query := (&TagClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withLabel = query
_q.withTag = query
return _q
}
@@ -628,7 +628,7 @@ func (_q *ItemQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Item, e
_q.withGroup != nil,
_q.withParent != nil,
_q.withChildren != nil,
_q.withLabel != nil,
_q.withTag != nil,
_q.withLocation != nil,
_q.withFields != nil,
_q.withMaintenanceEntries != nil,
@@ -678,10 +678,10 @@ func (_q *ItemQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Item, e
return nil, err
}
}
if query := _q.withLabel; query != nil {
if err := _q.loadLabel(ctx, query, nodes,
func(n *Item) { n.Edges.Label = []*Label{} },
func(n *Item, e *Label) { n.Edges.Label = append(n.Edges.Label, e) }); err != nil {
if query := _q.withTag; query != nil {
if err := _q.loadTag(ctx, query, nodes,
func(n *Item) { n.Edges.Tag = []*Tag{} },
func(n *Item, e *Tag) { n.Edges.Tag = append(n.Edges.Tag, e) }); err != nil {
return nil, err
}
}
@@ -810,7 +810,7 @@ func (_q *ItemQuery) loadChildren(ctx context.Context, query *ItemQuery, nodes [
}
return nil
}
func (_q *ItemQuery) loadLabel(ctx context.Context, query *LabelQuery, nodes []*Item, init func(*Item), assign func(*Item, *Label)) error {
func (_q *ItemQuery) loadTag(ctx context.Context, query *TagQuery, nodes []*Item, init func(*Item), assign func(*Item, *Tag)) error {
edgeIDs := make([]driver.Value, len(nodes))
byID := make(map[uuid.UUID]*Item)
nids := make(map[uuid.UUID]map[*Item]struct{})
@@ -822,11 +822,11 @@ func (_q *ItemQuery) loadLabel(ctx context.Context, query *LabelQuery, nodes []*
}
}
query.Where(func(s *sql.Selector) {
joinT := sql.Table(item.LabelTable)
s.Join(joinT).On(s.C(label.FieldID), joinT.C(item.LabelPrimaryKey[0]))
s.Where(sql.InValues(joinT.C(item.LabelPrimaryKey[1]), edgeIDs...))
joinT := sql.Table(item.TagTable)
s.Join(joinT).On(s.C(tag.FieldID), joinT.C(item.TagPrimaryKey[0]))
s.Where(sql.InValues(joinT.C(item.TagPrimaryKey[1]), edgeIDs...))
columns := s.SelectedColumns()
s.Select(joinT.C(item.LabelPrimaryKey[1]))
s.Select(joinT.C(item.TagPrimaryKey[1]))
s.AppendSelect(columns...)
s.SetDistinct(false)
})
@@ -856,14 +856,14 @@ func (_q *ItemQuery) loadLabel(ctx context.Context, query *LabelQuery, nodes []*
}
})
})
neighbors, err := withInterceptors[[]*Label](ctx, query, qr, query.inters)
neighbors, err := withInterceptors[[]*Tag](ctx, query, qr, query.inters)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nids[n.ID]
if !ok {
return fmt.Errorf(`unexpected "label" node returned %v`, n.ID)
return fmt.Errorf(`unexpected "tag" node returned %v`, n.ID)
}
for kn := range nodes {
assign(kn, n)

View File

@@ -16,10 +16,10 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemfield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// ItemUpdate is the builder for updating Item entities.
@@ -500,19 +500,19 @@ func (_u *ItemUpdate) AddChildren(v ...*Item) *ItemUpdate {
return _u.AddChildIDs(ids...)
}
// AddLabelIDs adds the "label" edge to the Label entity by IDs.
func (_u *ItemUpdate) AddLabelIDs(ids ...uuid.UUID) *ItemUpdate {
_u.mutation.AddLabelIDs(ids...)
// AddTagIDs adds the "tag" edge to the Tag entity by IDs.
func (_u *ItemUpdate) AddTagIDs(ids ...uuid.UUID) *ItemUpdate {
_u.mutation.AddTagIDs(ids...)
return _u
}
// AddLabel adds the "label" edges to the Label entity.
func (_u *ItemUpdate) AddLabel(v ...*Label) *ItemUpdate {
// AddTag adds the "tag" edges to the Tag entity.
func (_u *ItemUpdate) AddTag(v ...*Tag) *ItemUpdate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddLabelIDs(ids...)
return _u.AddTagIDs(ids...)
}
// SetLocationID sets the "location" edge to the Location entity by ID.
@@ -617,25 +617,25 @@ func (_u *ItemUpdate) RemoveChildren(v ...*Item) *ItemUpdate {
return _u.RemoveChildIDs(ids...)
}
// ClearLabel clears all "label" edges to the Label entity.
func (_u *ItemUpdate) ClearLabel() *ItemUpdate {
_u.mutation.ClearLabel()
// ClearTag clears all "tag" edges to the Tag entity.
func (_u *ItemUpdate) ClearTag() *ItemUpdate {
_u.mutation.ClearTag()
return _u
}
// RemoveLabelIDs removes the "label" edge to Label entities by IDs.
func (_u *ItemUpdate) RemoveLabelIDs(ids ...uuid.UUID) *ItemUpdate {
_u.mutation.RemoveLabelIDs(ids...)
// RemoveTagIDs removes the "tag" edge to Tag entities by IDs.
func (_u *ItemUpdate) RemoveTagIDs(ids ...uuid.UUID) *ItemUpdate {
_u.mutation.RemoveTagIDs(ids...)
return _u
}
// RemoveLabel removes "label" edges to Label entities.
func (_u *ItemUpdate) RemoveLabel(v ...*Label) *ItemUpdate {
// RemoveTag removes "tag" edges to Tag entities.
func (_u *ItemUpdate) RemoveTag(v ...*Tag) *ItemUpdate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveLabelIDs(ids...)
return _u.RemoveTagIDs(ids...)
}
// ClearLocation clears the "location" edge to the Location entity.
@@ -1031,28 +1031,28 @@ func (_u *ItemUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.LabelCleared() {
if _u.mutation.TagCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: true,
Table: item.LabelTable,
Columns: item.LabelPrimaryKey,
Table: item.TagTable,
Columns: item.TagPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedLabelIDs(); len(nodes) > 0 && !_u.mutation.LabelCleared() {
if nodes := _u.mutation.RemovedTagIDs(); len(nodes) > 0 && !_u.mutation.TagCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: true,
Table: item.LabelTable,
Columns: item.LabelPrimaryKey,
Table: item.TagTable,
Columns: item.TagPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1060,15 +1060,15 @@ func (_u *ItemUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.LabelIDs(); len(nodes) > 0 {
if nodes := _u.mutation.TagIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: true,
Table: item.LabelTable,
Columns: item.LabelPrimaryKey,
Table: item.TagTable,
Columns: item.TagPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1725,19 +1725,19 @@ func (_u *ItemUpdateOne) AddChildren(v ...*Item) *ItemUpdateOne {
return _u.AddChildIDs(ids...)
}
// AddLabelIDs adds the "label" edge to the Label entity by IDs.
func (_u *ItemUpdateOne) AddLabelIDs(ids ...uuid.UUID) *ItemUpdateOne {
_u.mutation.AddLabelIDs(ids...)
// AddTagIDs adds the "tag" edge to the Tag entity by IDs.
func (_u *ItemUpdateOne) AddTagIDs(ids ...uuid.UUID) *ItemUpdateOne {
_u.mutation.AddTagIDs(ids...)
return _u
}
// AddLabel adds the "label" edges to the Label entity.
func (_u *ItemUpdateOne) AddLabel(v ...*Label) *ItemUpdateOne {
// AddTag adds the "tag" edges to the Tag entity.
func (_u *ItemUpdateOne) AddTag(v ...*Tag) *ItemUpdateOne {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddLabelIDs(ids...)
return _u.AddTagIDs(ids...)
}
// SetLocationID sets the "location" edge to the Location entity by ID.
@@ -1842,25 +1842,25 @@ func (_u *ItemUpdateOne) RemoveChildren(v ...*Item) *ItemUpdateOne {
return _u.RemoveChildIDs(ids...)
}
// ClearLabel clears all "label" edges to the Label entity.
func (_u *ItemUpdateOne) ClearLabel() *ItemUpdateOne {
_u.mutation.ClearLabel()
// ClearTag clears all "tag" edges to the Tag entity.
func (_u *ItemUpdateOne) ClearTag() *ItemUpdateOne {
_u.mutation.ClearTag()
return _u
}
// RemoveLabelIDs removes the "label" edge to Label entities by IDs.
func (_u *ItemUpdateOne) RemoveLabelIDs(ids ...uuid.UUID) *ItemUpdateOne {
_u.mutation.RemoveLabelIDs(ids...)
// RemoveTagIDs removes the "tag" edge to Tag entities by IDs.
func (_u *ItemUpdateOne) RemoveTagIDs(ids ...uuid.UUID) *ItemUpdateOne {
_u.mutation.RemoveTagIDs(ids...)
return _u
}
// RemoveLabel removes "label" edges to Label entities.
func (_u *ItemUpdateOne) RemoveLabel(v ...*Label) *ItemUpdateOne {
// RemoveTag removes "tag" edges to Tag entities.
func (_u *ItemUpdateOne) RemoveTag(v ...*Tag) *ItemUpdateOne {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveLabelIDs(ids...)
return _u.RemoveTagIDs(ids...)
}
// ClearLocation clears the "location" edge to the Location entity.
@@ -2286,28 +2286,28 @@ func (_u *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.LabelCleared() {
if _u.mutation.TagCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: true,
Table: item.LabelTable,
Columns: item.LabelPrimaryKey,
Table: item.TagTable,
Columns: item.TagPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedLabelIDs(); len(nodes) > 0 && !_u.mutation.LabelCleared() {
if nodes := _u.mutation.RemovedTagIDs(); len(nodes) > 0 && !_u.mutation.TagCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: true,
Table: item.LabelTable,
Columns: item.LabelPrimaryKey,
Table: item.TagTable,
Columns: item.TagPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -2315,15 +2315,15 @@ func (_u *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error) {
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.LabelIDs(); len(nodes) > 0 {
if nodes := _u.mutation.TagIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: true,
Table: item.LabelTable,
Columns: item.LabelPrimaryKey,
Table: item.TagTable,
Columns: item.TagPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
IDSpec: sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -53,8 +53,8 @@ type ItemTemplate struct {
IncludePurchaseFields bool `json:"include_purchase_fields,omitempty"`
// Whether to include sold fields in items created from this template
IncludeSoldFields bool `json:"include_sold_fields,omitempty"`
// Default label IDs for items created from this template
DefaultLabelIds []uuid.UUID `json:"default_label_ids,omitempty"`
// Default tag IDs for items created from this template
DefaultTagIds []uuid.UUID `json:"default_tag_ids,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ItemTemplateQuery when eager-loading is set.
Edges ItemTemplateEdges `json:"edges"`
@@ -112,7 +112,7 @@ func (*ItemTemplate) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case itemtemplate.FieldDefaultLabelIds:
case itemtemplate.FieldDefaultTagIds:
values[i] = new([]byte)
case itemtemplate.FieldDefaultInsured, itemtemplate.FieldDefaultLifetimeWarranty, itemtemplate.FieldIncludeWarrantyFields, itemtemplate.FieldIncludePurchaseFields, itemtemplate.FieldIncludeSoldFields:
values[i] = new(sql.NullBool)
@@ -245,12 +245,12 @@ func (_m *ItemTemplate) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.IncludeSoldFields = value.Bool
}
case itemtemplate.FieldDefaultLabelIds:
case itemtemplate.FieldDefaultTagIds:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field default_label_ids", values[i])
return fmt.Errorf("unexpected type %T for field default_tag_ids", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.DefaultLabelIds); err != nil {
return fmt.Errorf("unmarshal field default_label_ids: %w", err)
if err := json.Unmarshal(*value, &_m.DefaultTagIds); err != nil {
return fmt.Errorf("unmarshal field default_tag_ids: %w", err)
}
}
case itemtemplate.ForeignKeys[0]:
@@ -366,8 +366,8 @@ func (_m *ItemTemplate) String() string {
builder.WriteString("include_sold_fields=")
builder.WriteString(fmt.Sprintf("%v", _m.IncludeSoldFields))
builder.WriteString(", ")
builder.WriteString("default_label_ids=")
builder.WriteString(fmt.Sprintf("%v", _m.DefaultLabelIds))
builder.WriteString("default_tag_ids=")
builder.WriteString(fmt.Sprintf("%v", _m.DefaultTagIds))
builder.WriteByte(')')
return builder.String()
}

View File

@@ -47,8 +47,8 @@ const (
FieldIncludePurchaseFields = "include_purchase_fields"
// FieldIncludeSoldFields holds the string denoting the include_sold_fields field in the database.
FieldIncludeSoldFields = "include_sold_fields"
// FieldDefaultLabelIds holds the string denoting the default_label_ids field in the database.
FieldDefaultLabelIds = "default_label_ids"
// FieldDefaultTagIds holds the string denoting the default_tag_ids field in the database.
FieldDefaultTagIds = "default_tag_ids"
// EdgeGroup holds the string denoting the group edge name in mutations.
EdgeGroup = "group"
// EdgeFields holds the string denoting the fields edge name in mutations.
@@ -99,7 +99,7 @@ var Columns = []string{
FieldIncludeWarrantyFields,
FieldIncludePurchaseFields,
FieldIncludeSoldFields,
FieldDefaultLabelIds,
FieldDefaultTagIds,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "item_templates"

View File

@@ -896,14 +896,14 @@ func IncludeSoldFieldsNEQ(v bool) predicate.ItemTemplate {
return predicate.ItemTemplate(sql.FieldNEQ(FieldIncludeSoldFields, v))
}
// DefaultLabelIdsIsNil applies the IsNil predicate on the "default_label_ids" field.
func DefaultLabelIdsIsNil() predicate.ItemTemplate {
return predicate.ItemTemplate(sql.FieldIsNull(FieldDefaultLabelIds))
// DefaultTagIdsIsNil applies the IsNil predicate on the "default_tag_ids" field.
func DefaultTagIdsIsNil() predicate.ItemTemplate {
return predicate.ItemTemplate(sql.FieldIsNull(FieldDefaultTagIds))
}
// DefaultLabelIdsNotNil applies the NotNil predicate on the "default_label_ids" field.
func DefaultLabelIdsNotNil() predicate.ItemTemplate {
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultLabelIds))
// DefaultTagIdsNotNil applies the NotNil predicate on the "default_tag_ids" field.
func DefaultTagIdsNotNil() predicate.ItemTemplate {
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultTagIds))
}
// HasGroup applies the HasEdge predicate on the "group" edge.

View File

@@ -240,9 +240,9 @@ func (_c *ItemTemplateCreate) SetNillableIncludeSoldFields(v *bool) *ItemTemplat
return _c
}
// SetDefaultLabelIds sets the "default_label_ids" field.
func (_c *ItemTemplateCreate) SetDefaultLabelIds(v []uuid.UUID) *ItemTemplateCreate {
_c.mutation.SetDefaultLabelIds(v)
// SetDefaultTagIds sets the "default_tag_ids" field.
func (_c *ItemTemplateCreate) SetDefaultTagIds(v []uuid.UUID) *ItemTemplateCreate {
_c.mutation.SetDefaultTagIds(v)
return _c
}
@@ -549,9 +549,9 @@ func (_c *ItemTemplateCreate) createSpec() (*ItemTemplate, *sqlgraph.CreateSpec)
_spec.SetField(itemtemplate.FieldIncludeSoldFields, field.TypeBool, value)
_node.IncludeSoldFields = value
}
if value, ok := _c.mutation.DefaultLabelIds(); ok {
_spec.SetField(itemtemplate.FieldDefaultLabelIds, field.TypeJSON, value)
_node.DefaultLabelIds = value
if value, ok := _c.mutation.DefaultTagIds(); ok {
_spec.SetField(itemtemplate.FieldDefaultTagIds, field.TypeJSON, value)
_node.DefaultTagIds = value
}
if nodes := _c.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{

View File

@@ -284,21 +284,21 @@ func (_u *ItemTemplateUpdate) SetNillableIncludeSoldFields(v *bool) *ItemTemplat
return _u
}
// SetDefaultLabelIds sets the "default_label_ids" field.
func (_u *ItemTemplateUpdate) SetDefaultLabelIds(v []uuid.UUID) *ItemTemplateUpdate {
_u.mutation.SetDefaultLabelIds(v)
// SetDefaultTagIds sets the "default_tag_ids" field.
func (_u *ItemTemplateUpdate) SetDefaultTagIds(v []uuid.UUID) *ItemTemplateUpdate {
_u.mutation.SetDefaultTagIds(v)
return _u
}
// AppendDefaultLabelIds appends value to the "default_label_ids" field.
func (_u *ItemTemplateUpdate) AppendDefaultLabelIds(v []uuid.UUID) *ItemTemplateUpdate {
_u.mutation.AppendDefaultLabelIds(v)
// AppendDefaultTagIds appends value to the "default_tag_ids" field.
func (_u *ItemTemplateUpdate) AppendDefaultTagIds(v []uuid.UUID) *ItemTemplateUpdate {
_u.mutation.AppendDefaultTagIds(v)
return _u
}
// ClearDefaultLabelIds clears the value of the "default_label_ids" field.
func (_u *ItemTemplateUpdate) ClearDefaultLabelIds() *ItemTemplateUpdate {
_u.mutation.ClearDefaultLabelIds()
// ClearDefaultTagIds clears the value of the "default_tag_ids" field.
func (_u *ItemTemplateUpdate) ClearDefaultTagIds() *ItemTemplateUpdate {
_u.mutation.ClearDefaultTagIds()
return _u
}
@@ -550,16 +550,16 @@ func (_u *ItemTemplateUpdate) sqlSave(ctx context.Context) (_node int, err error
if value, ok := _u.mutation.IncludeSoldFields(); ok {
_spec.SetField(itemtemplate.FieldIncludeSoldFields, field.TypeBool, value)
}
if value, ok := _u.mutation.DefaultLabelIds(); ok {
_spec.SetField(itemtemplate.FieldDefaultLabelIds, field.TypeJSON, value)
if value, ok := _u.mutation.DefaultTagIds(); ok {
_spec.SetField(itemtemplate.FieldDefaultTagIds, field.TypeJSON, value)
}
if value, ok := _u.mutation.AppendedDefaultLabelIds(); ok {
if value, ok := _u.mutation.AppendedDefaultTagIds(); ok {
_spec.AddModifier(func(u *sql.UpdateBuilder) {
sqljson.Append(u, itemtemplate.FieldDefaultLabelIds, value)
sqljson.Append(u, itemtemplate.FieldDefaultTagIds, value)
})
}
if _u.mutation.DefaultLabelIdsCleared() {
_spec.ClearField(itemtemplate.FieldDefaultLabelIds, field.TypeJSON)
if _u.mutation.DefaultTagIdsCleared() {
_spec.ClearField(itemtemplate.FieldDefaultTagIds, field.TypeJSON)
}
if _u.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
@@ -935,21 +935,21 @@ func (_u *ItemTemplateUpdateOne) SetNillableIncludeSoldFields(v *bool) *ItemTemp
return _u
}
// SetDefaultLabelIds sets the "default_label_ids" field.
func (_u *ItemTemplateUpdateOne) SetDefaultLabelIds(v []uuid.UUID) *ItemTemplateUpdateOne {
_u.mutation.SetDefaultLabelIds(v)
// SetDefaultTagIds sets the "default_tag_ids" field.
func (_u *ItemTemplateUpdateOne) SetDefaultTagIds(v []uuid.UUID) *ItemTemplateUpdateOne {
_u.mutation.SetDefaultTagIds(v)
return _u
}
// AppendDefaultLabelIds appends value to the "default_label_ids" field.
func (_u *ItemTemplateUpdateOne) AppendDefaultLabelIds(v []uuid.UUID) *ItemTemplateUpdateOne {
_u.mutation.AppendDefaultLabelIds(v)
// AppendDefaultTagIds appends value to the "default_tag_ids" field.
func (_u *ItemTemplateUpdateOne) AppendDefaultTagIds(v []uuid.UUID) *ItemTemplateUpdateOne {
_u.mutation.AppendDefaultTagIds(v)
return _u
}
// ClearDefaultLabelIds clears the value of the "default_label_ids" field.
func (_u *ItemTemplateUpdateOne) ClearDefaultLabelIds() *ItemTemplateUpdateOne {
_u.mutation.ClearDefaultLabelIds()
// ClearDefaultTagIds clears the value of the "default_tag_ids" field.
func (_u *ItemTemplateUpdateOne) ClearDefaultTagIds() *ItemTemplateUpdateOne {
_u.mutation.ClearDefaultTagIds()
return _u
}
@@ -1231,16 +1231,16 @@ func (_u *ItemTemplateUpdateOne) sqlSave(ctx context.Context) (_node *ItemTempla
if value, ok := _u.mutation.IncludeSoldFields(); ok {
_spec.SetField(itemtemplate.FieldIncludeSoldFields, field.TypeBool, value)
}
if value, ok := _u.mutation.DefaultLabelIds(); ok {
_spec.SetField(itemtemplate.FieldDefaultLabelIds, field.TypeJSON, value)
if value, ok := _u.mutation.DefaultTagIds(); ok {
_spec.SetField(itemtemplate.FieldDefaultTagIds, field.TypeJSON, value)
}
if value, ok := _u.mutation.AppendedDefaultLabelIds(); ok {
if value, ok := _u.mutation.AppendedDefaultTagIds(); ok {
_spec.AddModifier(func(u *sql.UpdateBuilder) {
sqljson.Append(u, itemtemplate.FieldDefaultLabelIds, value)
sqljson.Append(u, itemtemplate.FieldDefaultTagIds, value)
})
}
if _u.mutation.DefaultLabelIdsCleared() {
_spec.ClearField(itemtemplate.FieldDefaultLabelIds, field.TypeJSON)
if _u.mutation.DefaultTagIdsCleared() {
_spec.ClearField(itemtemplate.FieldDefaultTagIds, field.TypeJSON)
}
if _u.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{

View File

@@ -1,438 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package label
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.Label {
return predicate.Label(sql.FieldLTE(FieldID, id))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldUpdatedAt, v))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldName, v))
}
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
func Description(v string) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldDescription, v))
}
// Color applies equality check predicate on the "color" field. It's identical to ColorEQ.
func Color(v string) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldColor, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.Label {
return predicate.Label(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.Label {
return predicate.Label(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.Label {
return predicate.Label(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.Label {
return predicate.Label(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.Label {
return predicate.Label(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.Label {
return predicate.Label(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.Label {
return predicate.Label(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.Label {
return predicate.Label(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.Label {
return predicate.Label(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.Label {
return predicate.Label(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.Label {
return predicate.Label(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.Label {
return predicate.Label(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.Label {
return predicate.Label(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.Label {
return predicate.Label(sql.FieldLTE(FieldUpdatedAt, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.Label {
return predicate.Label(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.Label {
return predicate.Label(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.Label {
return predicate.Label(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.Label {
return predicate.Label(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.Label {
return predicate.Label(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.Label {
return predicate.Label(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.Label {
return predicate.Label(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.Label {
return predicate.Label(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.Label {
return predicate.Label(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.Label {
return predicate.Label(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.Label {
return predicate.Label(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.Label {
return predicate.Label(sql.FieldContainsFold(FieldName, v))
}
// DescriptionEQ applies the EQ predicate on the "description" field.
func DescriptionEQ(v string) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldDescription, v))
}
// DescriptionNEQ applies the NEQ predicate on the "description" field.
func DescriptionNEQ(v string) predicate.Label {
return predicate.Label(sql.FieldNEQ(FieldDescription, v))
}
// DescriptionIn applies the In predicate on the "description" field.
func DescriptionIn(vs ...string) predicate.Label {
return predicate.Label(sql.FieldIn(FieldDescription, vs...))
}
// DescriptionNotIn applies the NotIn predicate on the "description" field.
func DescriptionNotIn(vs ...string) predicate.Label {
return predicate.Label(sql.FieldNotIn(FieldDescription, vs...))
}
// DescriptionGT applies the GT predicate on the "description" field.
func DescriptionGT(v string) predicate.Label {
return predicate.Label(sql.FieldGT(FieldDescription, v))
}
// DescriptionGTE applies the GTE predicate on the "description" field.
func DescriptionGTE(v string) predicate.Label {
return predicate.Label(sql.FieldGTE(FieldDescription, v))
}
// DescriptionLT applies the LT predicate on the "description" field.
func DescriptionLT(v string) predicate.Label {
return predicate.Label(sql.FieldLT(FieldDescription, v))
}
// DescriptionLTE applies the LTE predicate on the "description" field.
func DescriptionLTE(v string) predicate.Label {
return predicate.Label(sql.FieldLTE(FieldDescription, v))
}
// DescriptionContains applies the Contains predicate on the "description" field.
func DescriptionContains(v string) predicate.Label {
return predicate.Label(sql.FieldContains(FieldDescription, v))
}
// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field.
func DescriptionHasPrefix(v string) predicate.Label {
return predicate.Label(sql.FieldHasPrefix(FieldDescription, v))
}
// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field.
func DescriptionHasSuffix(v string) predicate.Label {
return predicate.Label(sql.FieldHasSuffix(FieldDescription, v))
}
// DescriptionIsNil applies the IsNil predicate on the "description" field.
func DescriptionIsNil() predicate.Label {
return predicate.Label(sql.FieldIsNull(FieldDescription))
}
// DescriptionNotNil applies the NotNil predicate on the "description" field.
func DescriptionNotNil() predicate.Label {
return predicate.Label(sql.FieldNotNull(FieldDescription))
}
// DescriptionEqualFold applies the EqualFold predicate on the "description" field.
func DescriptionEqualFold(v string) predicate.Label {
return predicate.Label(sql.FieldEqualFold(FieldDescription, v))
}
// DescriptionContainsFold applies the ContainsFold predicate on the "description" field.
func DescriptionContainsFold(v string) predicate.Label {
return predicate.Label(sql.FieldContainsFold(FieldDescription, v))
}
// ColorEQ applies the EQ predicate on the "color" field.
func ColorEQ(v string) predicate.Label {
return predicate.Label(sql.FieldEQ(FieldColor, v))
}
// ColorNEQ applies the NEQ predicate on the "color" field.
func ColorNEQ(v string) predicate.Label {
return predicate.Label(sql.FieldNEQ(FieldColor, v))
}
// ColorIn applies the In predicate on the "color" field.
func ColorIn(vs ...string) predicate.Label {
return predicate.Label(sql.FieldIn(FieldColor, vs...))
}
// ColorNotIn applies the NotIn predicate on the "color" field.
func ColorNotIn(vs ...string) predicate.Label {
return predicate.Label(sql.FieldNotIn(FieldColor, vs...))
}
// ColorGT applies the GT predicate on the "color" field.
func ColorGT(v string) predicate.Label {
return predicate.Label(sql.FieldGT(FieldColor, v))
}
// ColorGTE applies the GTE predicate on the "color" field.
func ColorGTE(v string) predicate.Label {
return predicate.Label(sql.FieldGTE(FieldColor, v))
}
// ColorLT applies the LT predicate on the "color" field.
func ColorLT(v string) predicate.Label {
return predicate.Label(sql.FieldLT(FieldColor, v))
}
// ColorLTE applies the LTE predicate on the "color" field.
func ColorLTE(v string) predicate.Label {
return predicate.Label(sql.FieldLTE(FieldColor, v))
}
// ColorContains applies the Contains predicate on the "color" field.
func ColorContains(v string) predicate.Label {
return predicate.Label(sql.FieldContains(FieldColor, v))
}
// ColorHasPrefix applies the HasPrefix predicate on the "color" field.
func ColorHasPrefix(v string) predicate.Label {
return predicate.Label(sql.FieldHasPrefix(FieldColor, v))
}
// ColorHasSuffix applies the HasSuffix predicate on the "color" field.
func ColorHasSuffix(v string) predicate.Label {
return predicate.Label(sql.FieldHasSuffix(FieldColor, v))
}
// ColorIsNil applies the IsNil predicate on the "color" field.
func ColorIsNil() predicate.Label {
return predicate.Label(sql.FieldIsNull(FieldColor))
}
// ColorNotNil applies the NotNil predicate on the "color" field.
func ColorNotNil() predicate.Label {
return predicate.Label(sql.FieldNotNull(FieldColor))
}
// ColorEqualFold applies the EqualFold predicate on the "color" field.
func ColorEqualFold(v string) predicate.Label {
return predicate.Label(sql.FieldEqualFold(FieldColor, v))
}
// ColorContainsFold applies the ContainsFold predicate on the "color" field.
func ColorContainsFold(v string) predicate.Label {
return predicate.Label(sql.FieldContainsFold(FieldColor, v))
}
// HasGroup applies the HasEdge predicate on the "group" edge.
func HasGroup() predicate.Label {
return predicate.Label(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates).
func HasGroupWith(preds ...predicate.Group) predicate.Label {
return predicate.Label(func(s *sql.Selector) {
step := newGroupStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasItems applies the HasEdge predicate on the "items" edge.
func HasItems() predicate.Label {
return predicate.Label(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2M, false, ItemsTable, ItemsPrimaryKey...),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasItemsWith applies the HasEdge predicate on the "items" edge with a given conditions (other predicates).
func HasItemsWith(preds ...predicate.Item) predicate.Label {
return predicate.Label(func(s *sql.Selector) {
step := newItemsStep()
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.Label) predicate.Label {
return predicate.Label(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Label) predicate.Label {
return predicate.Label(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Label) predicate.Label {
return predicate.Label(sql.NotPredicates(p))
}

View File

@@ -265,7 +265,7 @@ var (
{Name: "include_warranty_fields", Type: field.TypeBool, Default: false},
{Name: "include_purchase_fields", Type: field.TypeBool, Default: false},
{Name: "include_sold_fields", Type: field.TypeBool, Default: false},
{Name: "default_label_ids", Type: field.TypeJSON, Nullable: true},
{Name: "default_tag_ids", Type: field.TypeJSON, Nullable: true},
{Name: "group_item_templates", Type: field.TypeUUID},
{Name: "item_template_location", Type: field.TypeUUID, Nullable: true},
}
@@ -296,30 +296,6 @@ var (
},
},
}
// LabelsColumns holds the columns for the "labels" table.
LabelsColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
{Name: "name", Type: field.TypeString, Size: 255},
{Name: "description", Type: field.TypeString, Nullable: true, Size: 1000},
{Name: "color", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "group_labels", Type: field.TypeUUID},
}
// LabelsTable holds the schema information for the "labels" table.
LabelsTable = &schema.Table{
Name: "labels",
Columns: LabelsColumns,
PrimaryKey: []*schema.Column{LabelsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "labels_groups_labels",
Columns: []*schema.Column{LabelsColumns[6]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.Cascade,
},
},
}
// LocationsColumns holds the columns for the "locations" table.
LocationsColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
@@ -429,6 +405,30 @@ var (
},
},
}
// TagsColumns holds the columns for the "tags" table.
TagsColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
{Name: "name", Type: field.TypeString, Size: 255},
{Name: "description", Type: field.TypeString, Nullable: true, Size: 1000},
{Name: "color", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "group_tags", Type: field.TypeUUID},
}
// TagsTable holds the schema information for the "tags" table.
TagsTable = &schema.Table{
Name: "tags",
Columns: TagsColumns,
PrimaryKey: []*schema.Column{TagsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "tags_groups_tags",
Columns: []*schema.Column{TagsColumns[6]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.Cascade,
},
},
}
// TemplateFieldsColumns holds the columns for the "template_fields" table.
TemplateFieldsColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
@@ -491,26 +491,26 @@ var (
},
},
}
// LabelItemsColumns holds the columns for the "label_items" table.
LabelItemsColumns = []*schema.Column{
{Name: "label_id", Type: field.TypeUUID},
// TagItemsColumns holds the columns for the "tag_items" table.
TagItemsColumns = []*schema.Column{
{Name: "tag_id", Type: field.TypeUUID},
{Name: "item_id", Type: field.TypeUUID},
}
// LabelItemsTable holds the schema information for the "label_items" table.
LabelItemsTable = &schema.Table{
Name: "label_items",
Columns: LabelItemsColumns,
PrimaryKey: []*schema.Column{LabelItemsColumns[0], LabelItemsColumns[1]},
// TagItemsTable holds the schema information for the "tag_items" table.
TagItemsTable = &schema.Table{
Name: "tag_items",
Columns: TagItemsColumns,
PrimaryKey: []*schema.Column{TagItemsColumns[0], TagItemsColumns[1]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "label_items_label_id",
Columns: []*schema.Column{LabelItemsColumns[0]},
RefColumns: []*schema.Column{LabelsColumns[0]},
Symbol: "tag_items_tag_id",
Columns: []*schema.Column{TagItemsColumns[0]},
RefColumns: []*schema.Column{TagsColumns[0]},
OnDelete: schema.Cascade,
},
{
Symbol: "label_items_item_id",
Columns: []*schema.Column{LabelItemsColumns[1]},
Symbol: "tag_items_item_id",
Columns: []*schema.Column{TagItemsColumns[1]},
RefColumns: []*schema.Column{ItemsColumns[0]},
OnDelete: schema.Cascade,
},
@@ -526,13 +526,13 @@ var (
ItemsTable,
ItemFieldsTable,
ItemTemplatesTable,
LabelsTable,
LocationsTable,
MaintenanceEntriesTable,
NotifiersTable,
TagsTable,
TemplateFieldsTable,
UsersTable,
LabelItemsTable,
TagItemsTable,
}
)
@@ -548,14 +548,14 @@ func init() {
ItemFieldsTable.ForeignKeys[0].RefTable = ItemsTable
ItemTemplatesTable.ForeignKeys[0].RefTable = GroupsTable
ItemTemplatesTable.ForeignKeys[1].RefTable = LocationsTable
LabelsTable.ForeignKeys[0].RefTable = GroupsTable
LocationsTable.ForeignKeys[0].RefTable = GroupsTable
LocationsTable.ForeignKeys[1].RefTable = LocationsTable
MaintenanceEntriesTable.ForeignKeys[0].RefTable = ItemsTable
NotifiersTable.ForeignKeys[0].RefTable = GroupsTable
NotifiersTable.ForeignKeys[1].RefTable = UsersTable
TagsTable.ForeignKeys[0].RefTable = GroupsTable
TemplateFieldsTable.ForeignKeys[0].RefTable = ItemTemplatesTable
UsersTable.ForeignKeys[0].RefTable = GroupsTable
LabelItemsTable.ForeignKeys[0].RefTable = LabelsTable
LabelItemsTable.ForeignKeys[1].RefTable = ItemsTable
TagItemsTable.ForeignKeys[0].RefTable = TagsTable
TagItemsTable.ForeignKeys[1].RefTable = ItemsTable
}

File diff suppressed because it is too large Load Diff

View File

@@ -30,9 +30,6 @@ type ItemField func(*sql.Selector)
// ItemTemplate is the predicate function for itemtemplate builders.
type ItemTemplate func(*sql.Selector)
// Label is the predicate function for label builders.
type Label func(*sql.Selector)
// Location is the predicate function for location builders.
type Location func(*sql.Selector)
@@ -42,6 +39,9 @@ type MaintenanceEntry func(*sql.Selector)
// Notifier is the predicate function for notifier builders.
type Notifier func(*sql.Selector)
// Tag is the predicate function for tag builders.
type Tag func(*sql.Selector)
// TemplateField is the predicate function for templatefield builders.
type TemplateField func(*sql.Selector)

View File

@@ -13,11 +13,11 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemfield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/templatefield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
)
@@ -403,53 +403,6 @@ func init() {
itemtemplateDescID := itemtemplateMixinFields0[0].Descriptor()
// itemtemplate.DefaultID holds the default value on creation for the id field.
itemtemplate.DefaultID = itemtemplateDescID.Default.(func() uuid.UUID)
labelMixin := schema.Label{}.Mixin()
labelMixinFields0 := labelMixin[0].Fields()
_ = labelMixinFields0
labelMixinFields1 := labelMixin[1].Fields()
_ = labelMixinFields1
labelFields := schema.Label{}.Fields()
_ = labelFields
// labelDescCreatedAt is the schema descriptor for created_at field.
labelDescCreatedAt := labelMixinFields0[1].Descriptor()
// label.DefaultCreatedAt holds the default value on creation for the created_at field.
label.DefaultCreatedAt = labelDescCreatedAt.Default.(func() time.Time)
// labelDescUpdatedAt is the schema descriptor for updated_at field.
labelDescUpdatedAt := labelMixinFields0[2].Descriptor()
// label.DefaultUpdatedAt holds the default value on creation for the updated_at field.
label.DefaultUpdatedAt = labelDescUpdatedAt.Default.(func() time.Time)
// label.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
label.UpdateDefaultUpdatedAt = labelDescUpdatedAt.UpdateDefault.(func() time.Time)
// labelDescName is the schema descriptor for name field.
labelDescName := labelMixinFields1[0].Descriptor()
// label.NameValidator is a validator for the "name" field. It is called by the builders before save.
label.NameValidator = func() func(string) error {
validators := labelDescName.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(name string) error {
for _, fn := range fns {
if err := fn(name); err != nil {
return err
}
}
return nil
}
}()
// labelDescDescription is the schema descriptor for description field.
labelDescDescription := labelMixinFields1[1].Descriptor()
// label.DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
label.DescriptionValidator = labelDescDescription.Validators[0].(func(string) error)
// labelDescColor is the schema descriptor for color field.
labelDescColor := labelFields[0].Descriptor()
// label.ColorValidator is a validator for the "color" field. It is called by the builders before save.
label.ColorValidator = labelDescColor.Validators[0].(func(string) error)
// labelDescID is the schema descriptor for id field.
labelDescID := labelMixinFields0[0].Descriptor()
// label.DefaultID holds the default value on creation for the id field.
label.DefaultID = labelDescID.Default.(func() uuid.UUID)
locationMixin := schema.Location{}.Mixin()
locationMixinFields0 := locationMixin[0].Fields()
_ = locationMixinFields0
@@ -597,6 +550,53 @@ func init() {
notifierDescID := notifierMixinFields0[0].Descriptor()
// notifier.DefaultID holds the default value on creation for the id field.
notifier.DefaultID = notifierDescID.Default.(func() uuid.UUID)
tagMixin := schema.Tag{}.Mixin()
tagMixinFields0 := tagMixin[0].Fields()
_ = tagMixinFields0
tagMixinFields1 := tagMixin[1].Fields()
_ = tagMixinFields1
tagFields := schema.Tag{}.Fields()
_ = tagFields
// tagDescCreatedAt is the schema descriptor for created_at field.
tagDescCreatedAt := tagMixinFields0[1].Descriptor()
// tag.DefaultCreatedAt holds the default value on creation for the created_at field.
tag.DefaultCreatedAt = tagDescCreatedAt.Default.(func() time.Time)
// tagDescUpdatedAt is the schema descriptor for updated_at field.
tagDescUpdatedAt := tagMixinFields0[2].Descriptor()
// tag.DefaultUpdatedAt holds the default value on creation for the updated_at field.
tag.DefaultUpdatedAt = tagDescUpdatedAt.Default.(func() time.Time)
// tag.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
tag.UpdateDefaultUpdatedAt = tagDescUpdatedAt.UpdateDefault.(func() time.Time)
// tagDescName is the schema descriptor for name field.
tagDescName := tagMixinFields1[0].Descriptor()
// tag.NameValidator is a validator for the "name" field. It is called by the builders before save.
tag.NameValidator = func() func(string) error {
validators := tagDescName.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(name string) error {
for _, fn := range fns {
if err := fn(name); err != nil {
return err
}
}
return nil
}
}()
// tagDescDescription is the schema descriptor for description field.
tagDescDescription := tagMixinFields1[1].Descriptor()
// tag.DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
tag.DescriptionValidator = tagDescDescription.Validators[0].(func(string) error)
// tagDescColor is the schema descriptor for color field.
tagDescColor := tagFields[0].Descriptor()
// tag.ColorValidator is a validator for the "color" field. It is called by the builders before save.
tag.ColorValidator = tagDescColor.Validators[0].(func(string) error)
// tagDescID is the schema descriptor for id field.
tagDescID := tagMixinFields0[0].Descriptor()
// tag.DefaultID holds the default value on creation for the id field.
tag.DefaultID = tagDescID.Default.(func() uuid.UUID)
templatefieldMixin := schema.TemplateField{}.Mixin()
templatefieldMixinFields0 := templatefieldMixin[0].Fields()
_ = templatefieldMixinFields0

View File

@@ -45,7 +45,7 @@ func (Group) Edges() []ent.Edge {
owned("users", User.Type),
owned("locations", Location.Type),
owned("items", Item.Type),
owned("labels", Label.Type),
owned("tags", Tag.Type),
owned("invitation_tokens", GroupInvitationToken.Type),
owned("notifiers", Notifier.Type),
owned("item_templates", ItemTemplate.Type),

View File

@@ -112,7 +112,7 @@ func (Item) Edges() []ent.Edge {
edge.To("children", Item.Type).
From("parent").
Unique(),
edge.From("label", Label.Type).
edge.From("tag", Tag.Type).
Ref("items"),
edge.From("location", Location.Type).
Ref("items").

View File

@@ -86,10 +86,10 @@ func (ItemTemplate) Fields() []ent.Field {
Comment("Whether to include sold fields in items created from this template"),
// ------------------------------------
// Default labels (stored as JSON array of UUIDs to allow reuse across templates)
field.JSON("default_label_ids", []uuid.UUID{}).
// Default tags (stored as JSON array of UUIDs to allow reuse across templates)
field.JSON("default_tag_ids", []uuid.UUID{}).
Optional().
Comment("Default label IDs for items created from this template"),
Comment("Default tag IDs for items created from this template"),
}
}

View File

@@ -7,21 +7,21 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema/mixins"
)
// Label holds the schema definition for the Label entity.
type Label struct {
// Tag holds the schema definition for the Tag entity.
type Tag struct {
ent.Schema
}
func (Label) Mixin() []ent.Mixin {
func (Tag) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.BaseMixin{},
mixins.DetailsMixin{},
GroupMixin{ref: "labels"},
GroupMixin{ref: "tags"},
}
}
// Fields of the Label.
func (Label) Fields() []ent.Field {
// Fields of the Tag.
func (Tag) Fields() []ent.Field {
return []ent.Field{
field.String("color").
MaxLen(255).
@@ -29,8 +29,8 @@ func (Label) Fields() []ent.Field {
}
}
// Edges of the Label.
func (Label) Edges() []ent.Edge {
// Edges of the Tag.
func (Tag) Edges() []ent.Edge {
return []ent.Edge{
edge.To("items", Item.Type),
}

View File

@@ -11,11 +11,11 @@ import (
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// Label is the model entity for the Label schema.
type Label struct {
// Tag is the model entity for the Tag schema.
type Tag struct {
config `json:"-"`
// ID of the ent.
ID uuid.UUID `json:"id,omitempty"`
@@ -30,14 +30,14 @@ type Label struct {
// Color holds the value of the "color" field.
Color string `json:"color,omitempty"`
// 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 LabelEdges `json:"edges"`
group_labels *uuid.UUID
// The values are being populated by the TagQuery when eager-loading is set.
Edges TagEdges `json:"edges"`
group_tags *uuid.UUID
selectValues sql.SelectValues
}
// LabelEdges holds the relations/edges for other nodes in the graph.
type LabelEdges struct {
// TagEdges holds the relations/edges for other nodes in the graph.
type TagEdges struct {
// Group holds the value of the group edge.
Group *Group `json:"group,omitempty"`
// Items holds the value of the items edge.
@@ -49,7 +49,7 @@ type LabelEdges struct {
// GroupOrErr returns the Group value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e LabelEdges) GroupOrErr() (*Group, error) {
func (e TagEdges) GroupOrErr() (*Group, error) {
if e.Group != nil {
return e.Group, nil
} else if e.loadedTypes[0] {
@@ -60,7 +60,7 @@ func (e LabelEdges) GroupOrErr() (*Group, error) {
// ItemsOrErr returns the Items value or an error if the edge
// was not loaded in eager-loading.
func (e LabelEdges) ItemsOrErr() ([]*Item, error) {
func (e TagEdges) ItemsOrErr() ([]*Item, error) {
if e.loadedTypes[1] {
return e.Items, nil
}
@@ -68,17 +68,17 @@ func (e LabelEdges) ItemsOrErr() ([]*Item, error) {
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Label) scanValues(columns []string) ([]any, error) {
func (*Tag) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case label.FieldName, label.FieldDescription, label.FieldColor:
case tag.FieldName, tag.FieldDescription, tag.FieldColor:
values[i] = new(sql.NullString)
case label.FieldCreatedAt, label.FieldUpdatedAt:
case tag.FieldCreatedAt, tag.FieldUpdatedAt:
values[i] = new(sql.NullTime)
case label.FieldID:
case tag.FieldID:
values[i] = new(uuid.UUID)
case label.ForeignKeys[0]: // group_labels
case tag.ForeignKeys[0]: // group_tags
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
values[i] = new(sql.UnknownType)
@@ -88,55 +88,55 @@ func (*Label) scanValues(columns []string) ([]any, error) {
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Label fields.
func (_m *Label) assignValues(columns []string, values []any) error {
// to the Tag fields.
func (_m *Tag) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case label.FieldID:
case tag.FieldID:
if value, ok := values[i].(*uuid.UUID); !ok {
return fmt.Errorf("unexpected type %T for field id", values[i])
} else if value != nil {
_m.ID = *value
}
case label.FieldCreatedAt:
case tag.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
case label.FieldUpdatedAt:
case tag.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
_m.UpdatedAt = value.Time
}
case label.FieldName:
case tag.FieldName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field name", values[i])
} else if value.Valid {
_m.Name = value.String
}
case label.FieldDescription:
case tag.FieldDescription:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field description", values[i])
} else if value.Valid {
_m.Description = value.String
}
case label.FieldColor:
case tag.FieldColor:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field color", values[i])
} else if value.Valid {
_m.Color = value.String
}
case label.ForeignKeys[0]:
case tag.ForeignKeys[0]:
if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field group_labels", values[i])
return fmt.Errorf("unexpected type %T for field group_tags", values[i])
} else if value.Valid {
_m.group_labels = new(uuid.UUID)
*_m.group_labels = *value.S.(*uuid.UUID)
_m.group_tags = new(uuid.UUID)
*_m.group_tags = *value.S.(*uuid.UUID)
}
default:
_m.selectValues.Set(columns[i], values[i])
@@ -145,44 +145,44 @@ func (_m *Label) assignValues(columns []string, values []any) error {
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the Label.
// Value returns the ent.Value that was dynamically selected and assigned to the Tag.
// This includes values selected through modifiers, order, etc.
func (_m *Label) Value(name string) (ent.Value, error) {
func (_m *Tag) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryGroup queries the "group" edge of the Label entity.
func (_m *Label) QueryGroup() *GroupQuery {
return NewLabelClient(_m.config).QueryGroup(_m)
// QueryGroup queries the "group" edge of the Tag entity.
func (_m *Tag) QueryGroup() *GroupQuery {
return NewTagClient(_m.config).QueryGroup(_m)
}
// QueryItems queries the "items" edge of the Label entity.
func (_m *Label) QueryItems() *ItemQuery {
return NewLabelClient(_m.config).QueryItems(_m)
// QueryItems queries the "items" edge of the Tag entity.
func (_m *Tag) QueryItems() *ItemQuery {
return NewTagClient(_m.config).QueryItems(_m)
}
// Update returns a builder for updating this Label.
// Note that you need to call Label.Unwrap() before calling this method if this Label
// Update returns a builder for updating this Tag.
// Note that you need to call Tag.Unwrap() before calling this method if this Tag
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *Label) Update() *LabelUpdateOne {
return NewLabelClient(_m.config).UpdateOne(_m)
func (_m *Tag) Update() *TagUpdateOne {
return NewTagClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the Label entity that was returned from a transaction after it was closed,
// Unwrap unwraps the Tag entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *Label) Unwrap() *Label {
func (_m *Tag) Unwrap() *Tag {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: Label is not a transactional entity")
panic("ent: Tag is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *Label) String() string {
func (_m *Tag) String() string {
var builder strings.Builder
builder.WriteString("Label(")
builder.WriteString("Tag(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
@@ -202,5 +202,5 @@ func (_m *Label) String() string {
return builder.String()
}
// Labels is a parsable slice of Label.
type Labels []*Label
// Tags is a parsable slice of Tag.
type Tags []*Tag

View File

@@ -1,6 +1,6 @@
// Code generated by ent, DO NOT EDIT.
package label
package tag
import (
"time"
@@ -11,8 +11,8 @@ import (
)
const (
// Label holds the string label denoting the label type in the database.
Label = "label"
// Label holds the string label denoting the tag type in the database.
Label = "tag"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
@@ -29,23 +29,23 @@ const (
EdgeGroup = "group"
// EdgeItems holds the string denoting the items edge name in mutations.
EdgeItems = "items"
// Table holds the table name of the label in the database.
Table = "labels"
// Table holds the table name of the tag in the database.
Table = "tags"
// GroupTable is the table that holds the group relation/edge.
GroupTable = "labels"
GroupTable = "tags"
// GroupInverseTable is the table name for the Group entity.
// It exists in this package in order to avoid circular dependency with the "group" package.
GroupInverseTable = "groups"
// GroupColumn is the table column denoting the group relation/edge.
GroupColumn = "group_labels"
GroupColumn = "group_tags"
// ItemsTable is the table that holds the items relation/edge. The primary key declared below.
ItemsTable = "label_items"
ItemsTable = "tag_items"
// ItemsInverseTable is the table name for the Item entity.
// It exists in this package in order to avoid circular dependency with the "item" package.
ItemsInverseTable = "items"
)
// Columns holds all SQL columns for label fields.
// Columns holds all SQL columns for tag fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
@@ -55,16 +55,16 @@ var Columns = []string{
FieldColor,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "labels"
// ForeignKeys holds the SQL foreign-keys that are owned by the "tags"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
"group_labels",
"group_tags",
}
var (
// ItemsPrimaryKey and ItemsColumn2 are the table columns denoting the
// primary key for the items relation (M2M).
ItemsPrimaryKey = []string{"label_id", "item_id"}
ItemsPrimaryKey = []string{"tag_id", "item_id"}
)
// ValidColumn reports if the column name is valid (part of the table columns).
@@ -99,7 +99,7 @@ var (
DefaultID func() uuid.UUID
)
// OrderOption defines the ordering options for the Label queries.
// OrderOption defines the ordering options for the Tag queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.

438
backend/internal/data/ent/tag/where.go generated Normal file
View File

@@ -0,0 +1,438 @@
// Code generated by ent, DO NOT EDIT.
package tag
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.Tag {
return predicate.Tag(sql.FieldLTE(FieldID, id))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldUpdatedAt, v))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldName, v))
}
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
func Description(v string) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldDescription, v))
}
// Color applies equality check predicate on the "color" field. It's identical to ColorEQ.
func Color(v string) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldColor, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.Tag {
return predicate.Tag(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.Tag {
return predicate.Tag(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.Tag {
return predicate.Tag(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.Tag {
return predicate.Tag(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.Tag {
return predicate.Tag(sql.FieldLTE(FieldUpdatedAt, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.Tag {
return predicate.Tag(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.Tag {
return predicate.Tag(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.Tag {
return predicate.Tag(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.Tag {
return predicate.Tag(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.Tag {
return predicate.Tag(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.Tag {
return predicate.Tag(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.Tag {
return predicate.Tag(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.Tag {
return predicate.Tag(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.Tag {
return predicate.Tag(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.Tag {
return predicate.Tag(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.Tag {
return predicate.Tag(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.Tag {
return predicate.Tag(sql.FieldContainsFold(FieldName, v))
}
// DescriptionEQ applies the EQ predicate on the "description" field.
func DescriptionEQ(v string) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldDescription, v))
}
// DescriptionNEQ applies the NEQ predicate on the "description" field.
func DescriptionNEQ(v string) predicate.Tag {
return predicate.Tag(sql.FieldNEQ(FieldDescription, v))
}
// DescriptionIn applies the In predicate on the "description" field.
func DescriptionIn(vs ...string) predicate.Tag {
return predicate.Tag(sql.FieldIn(FieldDescription, vs...))
}
// DescriptionNotIn applies the NotIn predicate on the "description" field.
func DescriptionNotIn(vs ...string) predicate.Tag {
return predicate.Tag(sql.FieldNotIn(FieldDescription, vs...))
}
// DescriptionGT applies the GT predicate on the "description" field.
func DescriptionGT(v string) predicate.Tag {
return predicate.Tag(sql.FieldGT(FieldDescription, v))
}
// DescriptionGTE applies the GTE predicate on the "description" field.
func DescriptionGTE(v string) predicate.Tag {
return predicate.Tag(sql.FieldGTE(FieldDescription, v))
}
// DescriptionLT applies the LT predicate on the "description" field.
func DescriptionLT(v string) predicate.Tag {
return predicate.Tag(sql.FieldLT(FieldDescription, v))
}
// DescriptionLTE applies the LTE predicate on the "description" field.
func DescriptionLTE(v string) predicate.Tag {
return predicate.Tag(sql.FieldLTE(FieldDescription, v))
}
// DescriptionContains applies the Contains predicate on the "description" field.
func DescriptionContains(v string) predicate.Tag {
return predicate.Tag(sql.FieldContains(FieldDescription, v))
}
// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field.
func DescriptionHasPrefix(v string) predicate.Tag {
return predicate.Tag(sql.FieldHasPrefix(FieldDescription, v))
}
// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field.
func DescriptionHasSuffix(v string) predicate.Tag {
return predicate.Tag(sql.FieldHasSuffix(FieldDescription, v))
}
// DescriptionIsNil applies the IsNil predicate on the "description" field.
func DescriptionIsNil() predicate.Tag {
return predicate.Tag(sql.FieldIsNull(FieldDescription))
}
// DescriptionNotNil applies the NotNil predicate on the "description" field.
func DescriptionNotNil() predicate.Tag {
return predicate.Tag(sql.FieldNotNull(FieldDescription))
}
// DescriptionEqualFold applies the EqualFold predicate on the "description" field.
func DescriptionEqualFold(v string) predicate.Tag {
return predicate.Tag(sql.FieldEqualFold(FieldDescription, v))
}
// DescriptionContainsFold applies the ContainsFold predicate on the "description" field.
func DescriptionContainsFold(v string) predicate.Tag {
return predicate.Tag(sql.FieldContainsFold(FieldDescription, v))
}
// ColorEQ applies the EQ predicate on the "color" field.
func ColorEQ(v string) predicate.Tag {
return predicate.Tag(sql.FieldEQ(FieldColor, v))
}
// ColorNEQ applies the NEQ predicate on the "color" field.
func ColorNEQ(v string) predicate.Tag {
return predicate.Tag(sql.FieldNEQ(FieldColor, v))
}
// ColorIn applies the In predicate on the "color" field.
func ColorIn(vs ...string) predicate.Tag {
return predicate.Tag(sql.FieldIn(FieldColor, vs...))
}
// ColorNotIn applies the NotIn predicate on the "color" field.
func ColorNotIn(vs ...string) predicate.Tag {
return predicate.Tag(sql.FieldNotIn(FieldColor, vs...))
}
// ColorGT applies the GT predicate on the "color" field.
func ColorGT(v string) predicate.Tag {
return predicate.Tag(sql.FieldGT(FieldColor, v))
}
// ColorGTE applies the GTE predicate on the "color" field.
func ColorGTE(v string) predicate.Tag {
return predicate.Tag(sql.FieldGTE(FieldColor, v))
}
// ColorLT applies the LT predicate on the "color" field.
func ColorLT(v string) predicate.Tag {
return predicate.Tag(sql.FieldLT(FieldColor, v))
}
// ColorLTE applies the LTE predicate on the "color" field.
func ColorLTE(v string) predicate.Tag {
return predicate.Tag(sql.FieldLTE(FieldColor, v))
}
// ColorContains applies the Contains predicate on the "color" field.
func ColorContains(v string) predicate.Tag {
return predicate.Tag(sql.FieldContains(FieldColor, v))
}
// ColorHasPrefix applies the HasPrefix predicate on the "color" field.
func ColorHasPrefix(v string) predicate.Tag {
return predicate.Tag(sql.FieldHasPrefix(FieldColor, v))
}
// ColorHasSuffix applies the HasSuffix predicate on the "color" field.
func ColorHasSuffix(v string) predicate.Tag {
return predicate.Tag(sql.FieldHasSuffix(FieldColor, v))
}
// ColorIsNil applies the IsNil predicate on the "color" field.
func ColorIsNil() predicate.Tag {
return predicate.Tag(sql.FieldIsNull(FieldColor))
}
// ColorNotNil applies the NotNil predicate on the "color" field.
func ColorNotNil() predicate.Tag {
return predicate.Tag(sql.FieldNotNull(FieldColor))
}
// ColorEqualFold applies the EqualFold predicate on the "color" field.
func ColorEqualFold(v string) predicate.Tag {
return predicate.Tag(sql.FieldEqualFold(FieldColor, v))
}
// ColorContainsFold applies the ContainsFold predicate on the "color" field.
func ColorContainsFold(v string) predicate.Tag {
return predicate.Tag(sql.FieldContainsFold(FieldColor, v))
}
// HasGroup applies the HasEdge predicate on the "group" edge.
func HasGroup() predicate.Tag {
return predicate.Tag(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates).
func HasGroupWith(preds ...predicate.Group) predicate.Tag {
return predicate.Tag(func(s *sql.Selector) {
step := newGroupStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasItems applies the HasEdge predicate on the "items" edge.
func HasItems() predicate.Tag {
return predicate.Tag(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2M, false, ItemsTable, ItemsPrimaryKey...),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasItemsWith applies the HasEdge predicate on the "items" edge with a given conditions (other predicates).
func HasItemsWith(preds ...predicate.Item) predicate.Tag {
return predicate.Tag(func(s *sql.Selector) {
step := newItemsStep()
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.Tag) predicate.Tag {
return predicate.Tag(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Tag) predicate.Tag {
return predicate.Tag(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Tag) predicate.Tag {
return predicate.Tag(sql.NotPredicates(p))
}

View File

@@ -13,24 +13,24 @@ import (
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// LabelCreate is the builder for creating a Label entity.
type LabelCreate struct {
// TagCreate is the builder for creating a Tag entity.
type TagCreate struct {
config
mutation *LabelMutation
mutation *TagMutation
hooks []Hook
}
// SetCreatedAt sets the "created_at" field.
func (_c *LabelCreate) SetCreatedAt(v time.Time) *LabelCreate {
func (_c *TagCreate) SetCreatedAt(v time.Time) *TagCreate {
_c.mutation.SetCreatedAt(v)
return _c
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (_c *LabelCreate) SetNillableCreatedAt(v *time.Time) *LabelCreate {
func (_c *TagCreate) SetNillableCreatedAt(v *time.Time) *TagCreate {
if v != nil {
_c.SetCreatedAt(*v)
}
@@ -38,13 +38,13 @@ func (_c *LabelCreate) SetNillableCreatedAt(v *time.Time) *LabelCreate {
}
// SetUpdatedAt sets the "updated_at" field.
func (_c *LabelCreate) SetUpdatedAt(v time.Time) *LabelCreate {
func (_c *TagCreate) SetUpdatedAt(v time.Time) *TagCreate {
_c.mutation.SetUpdatedAt(v)
return _c
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (_c *LabelCreate) SetNillableUpdatedAt(v *time.Time) *LabelCreate {
func (_c *TagCreate) SetNillableUpdatedAt(v *time.Time) *TagCreate {
if v != nil {
_c.SetUpdatedAt(*v)
}
@@ -52,19 +52,19 @@ func (_c *LabelCreate) SetNillableUpdatedAt(v *time.Time) *LabelCreate {
}
// SetName sets the "name" field.
func (_c *LabelCreate) SetName(v string) *LabelCreate {
func (_c *TagCreate) SetName(v string) *TagCreate {
_c.mutation.SetName(v)
return _c
}
// SetDescription sets the "description" field.
func (_c *LabelCreate) SetDescription(v string) *LabelCreate {
func (_c *TagCreate) SetDescription(v string) *TagCreate {
_c.mutation.SetDescription(v)
return _c
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_c *LabelCreate) SetNillableDescription(v *string) *LabelCreate {
func (_c *TagCreate) SetNillableDescription(v *string) *TagCreate {
if v != nil {
_c.SetDescription(*v)
}
@@ -72,13 +72,13 @@ func (_c *LabelCreate) SetNillableDescription(v *string) *LabelCreate {
}
// SetColor sets the "color" field.
func (_c *LabelCreate) SetColor(v string) *LabelCreate {
func (_c *TagCreate) SetColor(v string) *TagCreate {
_c.mutation.SetColor(v)
return _c
}
// SetNillableColor sets the "color" field if the given value is not nil.
func (_c *LabelCreate) SetNillableColor(v *string) *LabelCreate {
func (_c *TagCreate) SetNillableColor(v *string) *TagCreate {
if v != nil {
_c.SetColor(*v)
}
@@ -86,13 +86,13 @@ func (_c *LabelCreate) SetNillableColor(v *string) *LabelCreate {
}
// SetID sets the "id" field.
func (_c *LabelCreate) SetID(v uuid.UUID) *LabelCreate {
func (_c *TagCreate) SetID(v uuid.UUID) *TagCreate {
_c.mutation.SetID(v)
return _c
}
// SetNillableID sets the "id" field if the given value is not nil.
func (_c *LabelCreate) SetNillableID(v *uuid.UUID) *LabelCreate {
func (_c *TagCreate) SetNillableID(v *uuid.UUID) *TagCreate {
if v != nil {
_c.SetID(*v)
}
@@ -100,24 +100,24 @@ func (_c *LabelCreate) SetNillableID(v *uuid.UUID) *LabelCreate {
}
// SetGroupID sets the "group" edge to the Group entity by ID.
func (_c *LabelCreate) SetGroupID(id uuid.UUID) *LabelCreate {
func (_c *TagCreate) SetGroupID(id uuid.UUID) *TagCreate {
_c.mutation.SetGroupID(id)
return _c
}
// SetGroup sets the "group" edge to the Group entity.
func (_c *LabelCreate) SetGroup(v *Group) *LabelCreate {
func (_c *TagCreate) SetGroup(v *Group) *TagCreate {
return _c.SetGroupID(v.ID)
}
// AddItemIDs adds the "items" edge to the Item entity by IDs.
func (_c *LabelCreate) AddItemIDs(ids ...uuid.UUID) *LabelCreate {
func (_c *TagCreate) AddItemIDs(ids ...uuid.UUID) *TagCreate {
_c.mutation.AddItemIDs(ids...)
return _c
}
// AddItems adds the "items" edges to the Item entity.
func (_c *LabelCreate) AddItems(v ...*Item) *LabelCreate {
func (_c *TagCreate) AddItems(v ...*Item) *TagCreate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
@@ -125,19 +125,19 @@ func (_c *LabelCreate) AddItems(v ...*Item) *LabelCreate {
return _c.AddItemIDs(ids...)
}
// Mutation returns the LabelMutation object of the builder.
func (_c *LabelCreate) Mutation() *LabelMutation {
// Mutation returns the TagMutation object of the builder.
func (_c *TagCreate) Mutation() *TagMutation {
return _c.mutation
}
// Save creates the Label in the database.
func (_c *LabelCreate) Save(ctx context.Context) (*Label, error) {
// Save creates the Tag in the database.
func (_c *TagCreate) Save(ctx context.Context) (*Tag, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *LabelCreate) SaveX(ctx context.Context) *Label {
func (_c *TagCreate) SaveX(ctx context.Context) *Tag {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
@@ -146,67 +146,67 @@ func (_c *LabelCreate) SaveX(ctx context.Context) *Label {
}
// Exec executes the query.
func (_c *LabelCreate) Exec(ctx context.Context) error {
func (_c *TagCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *LabelCreate) ExecX(ctx context.Context) {
func (_c *TagCreate) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_c *LabelCreate) defaults() {
func (_c *TagCreate) defaults() {
if _, ok := _c.mutation.CreatedAt(); !ok {
v := label.DefaultCreatedAt()
v := tag.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
}
if _, ok := _c.mutation.UpdatedAt(); !ok {
v := label.DefaultUpdatedAt()
v := tag.DefaultUpdatedAt()
_c.mutation.SetUpdatedAt(v)
}
if _, ok := _c.mutation.ID(); !ok {
v := label.DefaultID()
v := tag.DefaultID()
_c.mutation.SetID(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *LabelCreate) check() error {
func (_c *TagCreate) check() error {
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Label.created_at"`)}
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Tag.created_at"`)}
}
if _, ok := _c.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Label.updated_at"`)}
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Tag.updated_at"`)}
}
if _, ok := _c.mutation.Name(); !ok {
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Label.name"`)}
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Tag.name"`)}
}
if v, ok := _c.mutation.Name(); ok {
if err := label.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Label.name": %w`, err)}
if err := tag.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Tag.name": %w`, err)}
}
}
if v, ok := _c.mutation.Description(); ok {
if err := label.DescriptionValidator(v); err != nil {
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Label.description": %w`, err)}
if err := tag.DescriptionValidator(v); err != nil {
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Tag.description": %w`, err)}
}
}
if v, ok := _c.mutation.Color(); ok {
if err := label.ColorValidator(v); err != nil {
return &ValidationError{Name: "color", err: fmt.Errorf(`ent: validator failed for field "Label.color": %w`, err)}
if err := tag.ColorValidator(v); err != nil {
return &ValidationError{Name: "color", err: fmt.Errorf(`ent: validator failed for field "Tag.color": %w`, err)}
}
}
if len(_c.mutation.GroupIDs()) == 0 {
return &ValidationError{Name: "group", err: errors.New(`ent: missing required edge "Label.group"`)}
return &ValidationError{Name: "group", err: errors.New(`ent: missing required edge "Tag.group"`)}
}
return nil
}
func (_c *LabelCreate) sqlSave(ctx context.Context) (*Label, error) {
func (_c *TagCreate) sqlSave(ctx context.Context) (*Tag, error) {
if err := _c.check(); err != nil {
return nil, err
}
@@ -229,41 +229,41 @@ func (_c *LabelCreate) sqlSave(ctx context.Context) (*Label, error) {
return _node, nil
}
func (_c *LabelCreate) createSpec() (*Label, *sqlgraph.CreateSpec) {
func (_c *TagCreate) createSpec() (*Tag, *sqlgraph.CreateSpec) {
var (
_node = &Label{config: _c.config}
_spec = sqlgraph.NewCreateSpec(label.Table, sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID))
_node = &Tag{config: _c.config}
_spec = sqlgraph.NewCreateSpec(tag.Table, sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID))
)
if id, ok := _c.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = &id
}
if value, ok := _c.mutation.CreatedAt(); ok {
_spec.SetField(label.FieldCreatedAt, field.TypeTime, value)
_spec.SetField(tag.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if value, ok := _c.mutation.UpdatedAt(); ok {
_spec.SetField(label.FieldUpdatedAt, field.TypeTime, value)
_spec.SetField(tag.FieldUpdatedAt, field.TypeTime, value)
_node.UpdatedAt = value
}
if value, ok := _c.mutation.Name(); ok {
_spec.SetField(label.FieldName, field.TypeString, value)
_spec.SetField(tag.FieldName, field.TypeString, value)
_node.Name = value
}
if value, ok := _c.mutation.Description(); ok {
_spec.SetField(label.FieldDescription, field.TypeString, value)
_spec.SetField(tag.FieldDescription, field.TypeString, value)
_node.Description = value
}
if value, ok := _c.mutation.Color(); ok {
_spec.SetField(label.FieldColor, field.TypeString, value)
_spec.SetField(tag.FieldColor, field.TypeString, value)
_node.Color = value
}
if nodes := _c.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: label.GroupTable,
Columns: []string{label.GroupColumn},
Table: tag.GroupTable,
Columns: []string{tag.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
@@ -272,15 +272,15 @@ func (_c *LabelCreate) createSpec() (*Label, *sqlgraph.CreateSpec) {
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.group_labels = &nodes[0]
_node.group_tags = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.ItemsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: false,
Table: label.ItemsTable,
Columns: label.ItemsPrimaryKey,
Table: tag.ItemsTable,
Columns: tag.ItemsPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
@@ -294,27 +294,27 @@ func (_c *LabelCreate) createSpec() (*Label, *sqlgraph.CreateSpec) {
return _node, _spec
}
// LabelCreateBulk is the builder for creating many Label entities in bulk.
type LabelCreateBulk struct {
// TagCreateBulk is the builder for creating many Tag entities in bulk.
type TagCreateBulk struct {
config
err error
builders []*LabelCreate
builders []*TagCreate
}
// Save creates the Label entities in the database.
func (_c *LabelCreateBulk) Save(ctx context.Context) ([]*Label, error) {
// Save creates the Tag entities in the database.
func (_c *TagCreateBulk) Save(ctx context.Context) ([]*Tag, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*Label, len(_c.builders))
nodes := make([]*Tag, len(_c.builders))
mutators := make([]Mutator, len(_c.builders))
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*LabelMutation)
mutation, ok := m.(*TagMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
@@ -357,7 +357,7 @@ func (_c *LabelCreateBulk) Save(ctx context.Context) ([]*Label, error) {
}
// SaveX is like Save, but panics if an error occurs.
func (_c *LabelCreateBulk) SaveX(ctx context.Context) []*Label {
func (_c *TagCreateBulk) SaveX(ctx context.Context) []*Tag {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
@@ -366,13 +366,13 @@ func (_c *LabelCreateBulk) SaveX(ctx context.Context) []*Label {
}
// Exec executes the query.
func (_c *LabelCreateBulk) Exec(ctx context.Context) error {
func (_c *TagCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *LabelCreateBulk) ExecX(ctx context.Context) {
func (_c *TagCreateBulk) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}

View File

@@ -8,30 +8,30 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// LabelDelete is the builder for deleting a Label entity.
type LabelDelete struct {
// TagDelete is the builder for deleting a Tag entity.
type TagDelete struct {
config
hooks []Hook
mutation *LabelMutation
mutation *TagMutation
}
// Where appends a list predicates to the LabelDelete builder.
func (_d *LabelDelete) Where(ps ...predicate.Label) *LabelDelete {
// Where appends a list predicates to the TagDelete builder.
func (_d *TagDelete) Where(ps ...predicate.Tag) *TagDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *LabelDelete) Exec(ctx context.Context) (int, error) {
func (_d *TagDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *LabelDelete) ExecX(ctx context.Context) int {
func (_d *TagDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
@@ -39,8 +39,8 @@ func (_d *LabelDelete) ExecX(ctx context.Context) int {
return n
}
func (_d *LabelDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(label.Table, sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID))
func (_d *TagDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(tag.Table, sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -56,32 +56,32 @@ func (_d *LabelDelete) sqlExec(ctx context.Context) (int, error) {
return affected, err
}
// LabelDeleteOne is the builder for deleting a single Label entity.
type LabelDeleteOne struct {
_d *LabelDelete
// TagDeleteOne is the builder for deleting a single Tag entity.
type TagDeleteOne struct {
_d *TagDelete
}
// Where appends a list predicates to the LabelDelete builder.
func (_d *LabelDeleteOne) Where(ps ...predicate.Label) *LabelDeleteOne {
// Where appends a list predicates to the TagDelete builder.
func (_d *TagDeleteOne) Where(ps ...predicate.Tag) *TagDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *LabelDeleteOne) Exec(ctx context.Context) error {
func (_d *TagDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{label.Label}
return &NotFoundError{tag.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *LabelDeleteOne) ExecX(ctx context.Context) {
func (_d *TagDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}

View File

@@ -15,17 +15,17 @@ import (
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// LabelQuery is the builder for querying Label entities.
type LabelQuery struct {
// TagQuery is the builder for querying Tag entities.
type TagQuery struct {
config
ctx *QueryContext
order []label.OrderOption
order []tag.OrderOption
inters []Interceptor
predicates []predicate.Label
predicates []predicate.Tag
withGroup *GroupQuery
withItems *ItemQuery
withFKs bool
@@ -34,39 +34,39 @@ type LabelQuery struct {
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the LabelQuery builder.
func (_q *LabelQuery) Where(ps ...predicate.Label) *LabelQuery {
// Where adds a new predicate for the TagQuery builder.
func (_q *TagQuery) Where(ps ...predicate.Tag) *TagQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *LabelQuery) Limit(limit int) *LabelQuery {
func (_q *TagQuery) Limit(limit int) *TagQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *LabelQuery) Offset(offset int) *LabelQuery {
func (_q *TagQuery) Offset(offset int) *TagQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *LabelQuery) Unique(unique bool) *LabelQuery {
func (_q *TagQuery) Unique(unique bool) *TagQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *LabelQuery) Order(o ...label.OrderOption) *LabelQuery {
func (_q *TagQuery) Order(o ...tag.OrderOption) *TagQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryGroup chains the current query on the "group" edge.
func (_q *LabelQuery) QueryGroup() *GroupQuery {
func (_q *TagQuery) QueryGroup() *GroupQuery {
query := (&GroupClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
@@ -77,9 +77,9 @@ func (_q *LabelQuery) QueryGroup() *GroupQuery {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(label.Table, label.FieldID, selector),
sqlgraph.From(tag.Table, tag.FieldID, selector),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, label.GroupTable, label.GroupColumn),
sqlgraph.Edge(sqlgraph.M2O, true, tag.GroupTable, tag.GroupColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
@@ -88,7 +88,7 @@ func (_q *LabelQuery) QueryGroup() *GroupQuery {
}
// QueryItems chains the current query on the "items" edge.
func (_q *LabelQuery) QueryItems() *ItemQuery {
func (_q *TagQuery) QueryItems() *ItemQuery {
query := (&ItemClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
@@ -99,9 +99,9 @@ func (_q *LabelQuery) QueryItems() *ItemQuery {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(label.Table, label.FieldID, selector),
sqlgraph.From(tag.Table, tag.FieldID, selector),
sqlgraph.To(item.Table, item.FieldID),
sqlgraph.Edge(sqlgraph.M2M, false, label.ItemsTable, label.ItemsPrimaryKey...),
sqlgraph.Edge(sqlgraph.M2M, false, tag.ItemsTable, tag.ItemsPrimaryKey...),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
@@ -109,21 +109,21 @@ func (_q *LabelQuery) QueryItems() *ItemQuery {
return query
}
// First returns the first Label entity from the query.
// Returns a *NotFoundError when no Label was found.
func (_q *LabelQuery) First(ctx context.Context) (*Label, error) {
// First returns the first Tag entity from the query.
// Returns a *NotFoundError when no Tag was found.
func (_q *TagQuery) First(ctx context.Context) (*Tag, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{label.Label}
return nil, &NotFoundError{tag.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *LabelQuery) FirstX(ctx context.Context) *Label {
func (_q *TagQuery) FirstX(ctx context.Context) *Tag {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
@@ -131,22 +131,22 @@ func (_q *LabelQuery) FirstX(ctx context.Context) *Label {
return node
}
// FirstID returns the first Label ID from the query.
// Returns a *NotFoundError when no Label ID was found.
func (_q *LabelQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
// FirstID returns the first Tag ID from the query.
// Returns a *NotFoundError when no Tag ID was found.
func (_q *TagQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{label.Label}
err = &NotFoundError{tag.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *LabelQuery) FirstIDX(ctx context.Context) uuid.UUID {
func (_q *TagQuery) FirstIDX(ctx context.Context) uuid.UUID {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
@@ -154,10 +154,10 @@ func (_q *LabelQuery) FirstIDX(ctx context.Context) uuid.UUID {
return id
}
// Only returns a single Label entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one Label entity is found.
// Returns a *NotFoundError when no Label entities are found.
func (_q *LabelQuery) Only(ctx context.Context) (*Label, error) {
// Only returns a single Tag entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one Tag entity is found.
// Returns a *NotFoundError when no Tag entities are found.
func (_q *TagQuery) Only(ctx context.Context) (*Tag, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
@@ -166,14 +166,14 @@ func (_q *LabelQuery) Only(ctx context.Context) (*Label, error) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{label.Label}
return nil, &NotFoundError{tag.Label}
default:
return nil, &NotSingularError{label.Label}
return nil, &NotSingularError{tag.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *LabelQuery) OnlyX(ctx context.Context) *Label {
func (_q *TagQuery) OnlyX(ctx context.Context) *Tag {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
@@ -181,10 +181,10 @@ func (_q *LabelQuery) OnlyX(ctx context.Context) *Label {
return node
}
// OnlyID is like Only, but returns the only Label ID in the query.
// Returns a *NotSingularError when more than one Label ID is found.
// OnlyID is like Only, but returns the only Tag ID in the query.
// Returns a *NotSingularError when more than one Tag ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *LabelQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
func (_q *TagQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
@@ -193,15 +193,15 @@ func (_q *LabelQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{label.Label}
err = &NotFoundError{tag.Label}
default:
err = &NotSingularError{label.Label}
err = &NotSingularError{tag.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *LabelQuery) OnlyIDX(ctx context.Context) uuid.UUID {
func (_q *TagQuery) OnlyIDX(ctx context.Context) uuid.UUID {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
@@ -209,18 +209,18 @@ func (_q *LabelQuery) OnlyIDX(ctx context.Context) uuid.UUID {
return id
}
// All executes the query and returns a list of Labels.
func (_q *LabelQuery) All(ctx context.Context) ([]*Label, error) {
// All executes the query and returns a list of Tags.
func (_q *TagQuery) All(ctx context.Context) ([]*Tag, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Label, *LabelQuery]()
return withInterceptors[[]*Label](ctx, _q, qr, _q.inters)
qr := querierAll[[]*Tag, *TagQuery]()
return withInterceptors[[]*Tag](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *LabelQuery) AllX(ctx context.Context) []*Label {
func (_q *TagQuery) AllX(ctx context.Context) []*Tag {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
@@ -228,20 +228,20 @@ func (_q *LabelQuery) AllX(ctx context.Context) []*Label {
return nodes
}
// IDs executes the query and returns a list of Label IDs.
func (_q *LabelQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
// IDs executes the query and returns a list of Tag IDs.
func (_q *TagQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(label.FieldID).Scan(ctx, &ids); err != nil {
if err = _q.Select(tag.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *LabelQuery) IDsX(ctx context.Context) []uuid.UUID {
func (_q *TagQuery) IDsX(ctx context.Context) []uuid.UUID {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
@@ -250,16 +250,16 @@ func (_q *LabelQuery) IDsX(ctx context.Context) []uuid.UUID {
}
// Count returns the count of the given query.
func (_q *LabelQuery) Count(ctx context.Context) (int, error) {
func (_q *TagQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*LabelQuery](), _q.inters)
return withInterceptors[int](ctx, _q, querierCount[*TagQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *LabelQuery) CountX(ctx context.Context) int {
func (_q *TagQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
@@ -268,7 +268,7 @@ func (_q *LabelQuery) CountX(ctx context.Context) int {
}
// Exist returns true if the query has elements in the graph.
func (_q *LabelQuery) Exist(ctx context.Context) (bool, error) {
func (_q *TagQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
@@ -281,7 +281,7 @@ func (_q *LabelQuery) Exist(ctx context.Context) (bool, error) {
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *LabelQuery) ExistX(ctx context.Context) bool {
func (_q *TagQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
@@ -289,18 +289,18 @@ func (_q *LabelQuery) ExistX(ctx context.Context) bool {
return exist
}
// Clone returns a duplicate of the LabelQuery builder, including all associated steps. It can be
// Clone returns a duplicate of the TagQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *LabelQuery) Clone() *LabelQuery {
func (_q *TagQuery) Clone() *TagQuery {
if _q == nil {
return nil
}
return &LabelQuery{
return &TagQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]label.OrderOption{}, _q.order...),
order: append([]tag.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.Label{}, _q.predicates...),
predicates: append([]predicate.Tag{}, _q.predicates...),
withGroup: _q.withGroup.Clone(),
withItems: _q.withItems.Clone(),
// clone intermediate query.
@@ -311,7 +311,7 @@ func (_q *LabelQuery) Clone() *LabelQuery {
// WithGroup tells the query-builder to eager-load the nodes that are connected to
// the "group" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *LabelQuery) WithGroup(opts ...func(*GroupQuery)) *LabelQuery {
func (_q *TagQuery) WithGroup(opts ...func(*GroupQuery)) *TagQuery {
query := (&GroupClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
@@ -322,7 +322,7 @@ func (_q *LabelQuery) WithGroup(opts ...func(*GroupQuery)) *LabelQuery {
// WithItems tells the query-builder to eager-load the nodes that are connected to
// the "items" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *LabelQuery) WithItems(opts ...func(*ItemQuery)) *LabelQuery {
func (_q *TagQuery) WithItems(opts ...func(*ItemQuery)) *TagQuery {
query := (&ItemClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
@@ -341,15 +341,15 @@ func (_q *LabelQuery) WithItems(opts ...func(*ItemQuery)) *LabelQuery {
// Count int `json:"count,omitempty"`
// }
//
// client.Label.Query().
// GroupBy(label.FieldCreatedAt).
// client.Tag.Query().
// GroupBy(tag.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *LabelQuery) GroupBy(field string, fields ...string) *LabelGroupBy {
func (_q *TagQuery) GroupBy(field string, fields ...string) *TagGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &LabelGroupBy{build: _q}
grbuild := &TagGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = label.Label
grbuild.label = tag.Label
grbuild.scan = grbuild.Scan
return grbuild
}
@@ -363,23 +363,23 @@ func (_q *LabelQuery) GroupBy(field string, fields ...string) *LabelGroupBy {
// CreatedAt time.Time `json:"created_at,omitempty"`
// }
//
// client.Label.Query().
// Select(label.FieldCreatedAt).
// client.Tag.Query().
// Select(tag.FieldCreatedAt).
// Scan(ctx, &v)
func (_q *LabelQuery) Select(fields ...string) *LabelSelect {
func (_q *TagQuery) Select(fields ...string) *TagSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &LabelSelect{LabelQuery: _q}
sbuild.label = label.Label
sbuild := &TagSelect{TagQuery: _q}
sbuild.label = tag.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a LabelSelect configured with the given aggregations.
func (_q *LabelQuery) Aggregate(fns ...AggregateFunc) *LabelSelect {
// Aggregate returns a TagSelect configured with the given aggregations.
func (_q *TagQuery) Aggregate(fns ...AggregateFunc) *TagSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *LabelQuery) prepareQuery(ctx context.Context) error {
func (_q *TagQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
@@ -391,7 +391,7 @@ func (_q *LabelQuery) prepareQuery(ctx context.Context) error {
}
}
for _, f := range _q.ctx.Fields {
if !label.ValidColumn(f) {
if !tag.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
@@ -405,9 +405,9 @@ func (_q *LabelQuery) prepareQuery(ctx context.Context) error {
return nil
}
func (_q *LabelQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Label, error) {
func (_q *TagQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Tag, error) {
var (
nodes = []*Label{}
nodes = []*Tag{}
withFKs = _q.withFKs
_spec = _q.querySpec()
loadedTypes = [2]bool{
@@ -419,13 +419,13 @@ func (_q *LabelQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Label,
withFKs = true
}
if withFKs {
_spec.Node.Columns = append(_spec.Node.Columns, label.ForeignKeys...)
_spec.Node.Columns = append(_spec.Node.Columns, tag.ForeignKeys...)
}
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*Label).scanValues(nil, columns)
return (*Tag).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &Label{config: _q.config}
node := &Tag{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
@@ -441,28 +441,28 @@ func (_q *LabelQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Label,
}
if query := _q.withGroup; query != nil {
if err := _q.loadGroup(ctx, query, nodes, nil,
func(n *Label, e *Group) { n.Edges.Group = e }); err != nil {
func(n *Tag, e *Group) { n.Edges.Group = e }); err != nil {
return nil, err
}
}
if query := _q.withItems; query != nil {
if err := _q.loadItems(ctx, query, nodes,
func(n *Label) { n.Edges.Items = []*Item{} },
func(n *Label, e *Item) { n.Edges.Items = append(n.Edges.Items, e) }); err != nil {
func(n *Tag) { n.Edges.Items = []*Item{} },
func(n *Tag, e *Item) { n.Edges.Items = append(n.Edges.Items, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *LabelQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*Label, init func(*Label), assign func(*Label, *Group)) error {
func (_q *TagQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*Tag, init func(*Tag), assign func(*Tag, *Group)) error {
ids := make([]uuid.UUID, 0, len(nodes))
nodeids := make(map[uuid.UUID][]*Label)
nodeids := make(map[uuid.UUID][]*Tag)
for i := range nodes {
if nodes[i].group_labels == nil {
if nodes[i].group_tags == nil {
continue
}
fk := *nodes[i].group_labels
fk := *nodes[i].group_tags
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
@@ -479,7 +479,7 @@ func (_q *LabelQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "group_labels" returned %v`, n.ID)
return fmt.Errorf(`unexpected foreign-key "group_tags" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
@@ -487,10 +487,10 @@ func (_q *LabelQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []
}
return nil
}
func (_q *LabelQuery) loadItems(ctx context.Context, query *ItemQuery, nodes []*Label, init func(*Label), assign func(*Label, *Item)) error {
func (_q *TagQuery) loadItems(ctx context.Context, query *ItemQuery, nodes []*Tag, init func(*Tag), assign func(*Tag, *Item)) error {
edgeIDs := make([]driver.Value, len(nodes))
byID := make(map[uuid.UUID]*Label)
nids := make(map[uuid.UUID]map[*Label]struct{})
byID := make(map[uuid.UUID]*Tag)
nids := make(map[uuid.UUID]map[*Tag]struct{})
for i, node := range nodes {
edgeIDs[i] = node.ID
byID[node.ID] = node
@@ -499,11 +499,11 @@ func (_q *LabelQuery) loadItems(ctx context.Context, query *ItemQuery, nodes []*
}
}
query.Where(func(s *sql.Selector) {
joinT := sql.Table(label.ItemsTable)
s.Join(joinT).On(s.C(item.FieldID), joinT.C(label.ItemsPrimaryKey[1]))
s.Where(sql.InValues(joinT.C(label.ItemsPrimaryKey[0]), edgeIDs...))
joinT := sql.Table(tag.ItemsTable)
s.Join(joinT).On(s.C(item.FieldID), joinT.C(tag.ItemsPrimaryKey[1]))
s.Where(sql.InValues(joinT.C(tag.ItemsPrimaryKey[0]), edgeIDs...))
columns := s.SelectedColumns()
s.Select(joinT.C(label.ItemsPrimaryKey[0]))
s.Select(joinT.C(tag.ItemsPrimaryKey[0]))
s.AppendSelect(columns...)
s.SetDistinct(false)
})
@@ -525,7 +525,7 @@ func (_q *LabelQuery) loadItems(ctx context.Context, query *ItemQuery, nodes []*
outValue := *values[0].(*uuid.UUID)
inValue := *values[1].(*uuid.UUID)
if nids[inValue] == nil {
nids[inValue] = map[*Label]struct{}{byID[outValue]: {}}
nids[inValue] = map[*Tag]struct{}{byID[outValue]: {}}
return assign(columns[1:], values[1:])
}
nids[inValue][byID[outValue]] = struct{}{}
@@ -549,7 +549,7 @@ func (_q *LabelQuery) loadItems(ctx context.Context, query *ItemQuery, nodes []*
return nil
}
func (_q *LabelQuery) sqlCount(ctx context.Context) (int, error) {
func (_q *TagQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
@@ -558,8 +558,8 @@ func (_q *LabelQuery) sqlCount(ctx context.Context) (int, error) {
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *LabelQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(label.Table, label.Columns, sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID))
func (_q *TagQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(tag.Table, tag.Columns, sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
@@ -568,9 +568,9 @@ func (_q *LabelQuery) querySpec() *sqlgraph.QuerySpec {
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, label.FieldID)
_spec.Node.Columns = append(_spec.Node.Columns, tag.FieldID)
for i := range fields {
if fields[i] != label.FieldID {
if fields[i] != tag.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
@@ -598,12 +598,12 @@ func (_q *LabelQuery) querySpec() *sqlgraph.QuerySpec {
return _spec
}
func (_q *LabelQuery) sqlQuery(ctx context.Context) *sql.Selector {
func (_q *TagQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(label.Table)
t1 := builder.Table(tag.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = label.Columns
columns = tag.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
@@ -630,28 +630,28 @@ func (_q *LabelQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
// LabelGroupBy is the group-by builder for Label entities.
type LabelGroupBy struct {
// TagGroupBy is the group-by builder for Tag entities.
type TagGroupBy struct {
selector
build *LabelQuery
build *TagQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *LabelGroupBy) Aggregate(fns ...AggregateFunc) *LabelGroupBy {
func (_g *TagGroupBy) Aggregate(fns ...AggregateFunc) *TagGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *LabelGroupBy) Scan(ctx context.Context, v any) error {
func (_g *TagGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*LabelQuery, *LabelGroupBy](ctx, _g.build, _g, _g.build.inters, v)
return scanWithInterceptors[*TagQuery, *TagGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *LabelGroupBy) sqlScan(ctx context.Context, root *LabelQuery, v any) error {
func (_g *TagGroupBy) sqlScan(ctx context.Context, root *TagQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
@@ -678,28 +678,28 @@ func (_g *LabelGroupBy) sqlScan(ctx context.Context, root *LabelQuery, v any) er
return sql.ScanSlice(rows, v)
}
// LabelSelect is the builder for selecting fields of Label entities.
type LabelSelect struct {
*LabelQuery
// TagSelect is the builder for selecting fields of Tag entities.
type TagSelect struct {
*TagQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *LabelSelect) Aggregate(fns ...AggregateFunc) *LabelSelect {
func (_s *TagSelect) Aggregate(fns ...AggregateFunc) *TagSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *LabelSelect) Scan(ctx context.Context, v any) error {
func (_s *TagSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*LabelQuery, *LabelSelect](ctx, _s.LabelQuery, _s, _s.inters, v)
return scanWithInterceptors[*TagQuery, *TagSelect](ctx, _s.TagQuery, _s, _s.inters, v)
}
func (_s *LabelSelect) sqlScan(ctx context.Context, root *LabelQuery, v any) error {
func (_s *TagSelect) sqlScan(ctx context.Context, root *TagQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {

View File

@@ -14,37 +14,37 @@ import (
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
)
// LabelUpdate is the builder for updating Label entities.
type LabelUpdate struct {
// TagUpdate is the builder for updating Tag entities.
type TagUpdate struct {
config
hooks []Hook
mutation *LabelMutation
mutation *TagMutation
}
// Where appends a list predicates to the LabelUpdate builder.
func (_u *LabelUpdate) Where(ps ...predicate.Label) *LabelUpdate {
// Where appends a list predicates to the TagUpdate builder.
func (_u *TagUpdate) Where(ps ...predicate.Tag) *TagUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *LabelUpdate) SetUpdatedAt(v time.Time) *LabelUpdate {
func (_u *TagUpdate) SetUpdatedAt(v time.Time) *TagUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetName sets the "name" field.
func (_u *LabelUpdate) SetName(v string) *LabelUpdate {
func (_u *TagUpdate) SetName(v string) *TagUpdate {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *LabelUpdate) SetNillableName(v *string) *LabelUpdate {
func (_u *TagUpdate) SetNillableName(v *string) *TagUpdate {
if v != nil {
_u.SetName(*v)
}
@@ -52,13 +52,13 @@ func (_u *LabelUpdate) SetNillableName(v *string) *LabelUpdate {
}
// SetDescription sets the "description" field.
func (_u *LabelUpdate) SetDescription(v string) *LabelUpdate {
func (_u *TagUpdate) SetDescription(v string) *TagUpdate {
_u.mutation.SetDescription(v)
return _u
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_u *LabelUpdate) SetNillableDescription(v *string) *LabelUpdate {
func (_u *TagUpdate) SetNillableDescription(v *string) *TagUpdate {
if v != nil {
_u.SetDescription(*v)
}
@@ -66,19 +66,19 @@ func (_u *LabelUpdate) SetNillableDescription(v *string) *LabelUpdate {
}
// ClearDescription clears the value of the "description" field.
func (_u *LabelUpdate) ClearDescription() *LabelUpdate {
func (_u *TagUpdate) ClearDescription() *TagUpdate {
_u.mutation.ClearDescription()
return _u
}
// SetColor sets the "color" field.
func (_u *LabelUpdate) SetColor(v string) *LabelUpdate {
func (_u *TagUpdate) SetColor(v string) *TagUpdate {
_u.mutation.SetColor(v)
return _u
}
// SetNillableColor sets the "color" field if the given value is not nil.
func (_u *LabelUpdate) SetNillableColor(v *string) *LabelUpdate {
func (_u *TagUpdate) SetNillableColor(v *string) *TagUpdate {
if v != nil {
_u.SetColor(*v)
}
@@ -86,30 +86,30 @@ func (_u *LabelUpdate) SetNillableColor(v *string) *LabelUpdate {
}
// ClearColor clears the value of the "color" field.
func (_u *LabelUpdate) ClearColor() *LabelUpdate {
func (_u *TagUpdate) ClearColor() *TagUpdate {
_u.mutation.ClearColor()
return _u
}
// SetGroupID sets the "group" edge to the Group entity by ID.
func (_u *LabelUpdate) SetGroupID(id uuid.UUID) *LabelUpdate {
func (_u *TagUpdate) SetGroupID(id uuid.UUID) *TagUpdate {
_u.mutation.SetGroupID(id)
return _u
}
// SetGroup sets the "group" edge to the Group entity.
func (_u *LabelUpdate) SetGroup(v *Group) *LabelUpdate {
func (_u *TagUpdate) SetGroup(v *Group) *TagUpdate {
return _u.SetGroupID(v.ID)
}
// AddItemIDs adds the "items" edge to the Item entity by IDs.
func (_u *LabelUpdate) AddItemIDs(ids ...uuid.UUID) *LabelUpdate {
func (_u *TagUpdate) AddItemIDs(ids ...uuid.UUID) *TagUpdate {
_u.mutation.AddItemIDs(ids...)
return _u
}
// AddItems adds the "items" edges to the Item entity.
func (_u *LabelUpdate) AddItems(v ...*Item) *LabelUpdate {
func (_u *TagUpdate) AddItems(v ...*Item) *TagUpdate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
@@ -117,31 +117,31 @@ func (_u *LabelUpdate) AddItems(v ...*Item) *LabelUpdate {
return _u.AddItemIDs(ids...)
}
// Mutation returns the LabelMutation object of the builder.
func (_u *LabelUpdate) Mutation() *LabelMutation {
// Mutation returns the TagMutation object of the builder.
func (_u *TagUpdate) Mutation() *TagMutation {
return _u.mutation
}
// ClearGroup clears the "group" edge to the Group entity.
func (_u *LabelUpdate) ClearGroup() *LabelUpdate {
func (_u *TagUpdate) ClearGroup() *TagUpdate {
_u.mutation.ClearGroup()
return _u
}
// ClearItems clears all "items" edges to the Item entity.
func (_u *LabelUpdate) ClearItems() *LabelUpdate {
func (_u *TagUpdate) ClearItems() *TagUpdate {
_u.mutation.ClearItems()
return _u
}
// RemoveItemIDs removes the "items" edge to Item entities by IDs.
func (_u *LabelUpdate) RemoveItemIDs(ids ...uuid.UUID) *LabelUpdate {
func (_u *TagUpdate) RemoveItemIDs(ids ...uuid.UUID) *TagUpdate {
_u.mutation.RemoveItemIDs(ids...)
return _u
}
// RemoveItems removes "items" edges to Item entities.
func (_u *LabelUpdate) RemoveItems(v ...*Item) *LabelUpdate {
func (_u *TagUpdate) RemoveItems(v ...*Item) *TagUpdate {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
@@ -150,13 +150,13 @@ func (_u *LabelUpdate) RemoveItems(v ...*Item) *LabelUpdate {
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *LabelUpdate) Save(ctx context.Context) (int, error) {
func (_u *TagUpdate) Save(ctx context.Context) (int, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *LabelUpdate) SaveX(ctx context.Context) int {
func (_u *TagUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
@@ -165,54 +165,54 @@ func (_u *LabelUpdate) SaveX(ctx context.Context) int {
}
// Exec executes the query.
func (_u *LabelUpdate) Exec(ctx context.Context) error {
func (_u *TagUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *LabelUpdate) ExecX(ctx context.Context) {
func (_u *TagUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *LabelUpdate) defaults() {
func (_u *TagUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := label.UpdateDefaultUpdatedAt()
v := tag.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *LabelUpdate) check() error {
func (_u *TagUpdate) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := label.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Label.name": %w`, err)}
if err := tag.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Tag.name": %w`, err)}
}
}
if v, ok := _u.mutation.Description(); ok {
if err := label.DescriptionValidator(v); err != nil {
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Label.description": %w`, err)}
if err := tag.DescriptionValidator(v); err != nil {
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Tag.description": %w`, err)}
}
}
if v, ok := _u.mutation.Color(); ok {
if err := label.ColorValidator(v); err != nil {
return &ValidationError{Name: "color", err: fmt.Errorf(`ent: validator failed for field "Label.color": %w`, err)}
if err := tag.ColorValidator(v); err != nil {
return &ValidationError{Name: "color", err: fmt.Errorf(`ent: validator failed for field "Tag.color": %w`, err)}
}
}
if _u.mutation.GroupCleared() && len(_u.mutation.GroupIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Label.group"`)
return errors.New(`ent: clearing a required unique edge "Tag.group"`)
}
return nil
}
func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
func (_u *TagUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(label.Table, label.Columns, sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID))
_spec := sqlgraph.NewUpdateSpec(tag.Table, tag.Columns, sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -221,29 +221,29 @@ func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(label.FieldUpdatedAt, field.TypeTime, value)
_spec.SetField(tag.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(label.FieldName, field.TypeString, value)
_spec.SetField(tag.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Description(); ok {
_spec.SetField(label.FieldDescription, field.TypeString, value)
_spec.SetField(tag.FieldDescription, field.TypeString, value)
}
if _u.mutation.DescriptionCleared() {
_spec.ClearField(label.FieldDescription, field.TypeString)
_spec.ClearField(tag.FieldDescription, field.TypeString)
}
if value, ok := _u.mutation.Color(); ok {
_spec.SetField(label.FieldColor, field.TypeString, value)
_spec.SetField(tag.FieldColor, field.TypeString, value)
}
if _u.mutation.ColorCleared() {
_spec.ClearField(label.FieldColor, field.TypeString)
_spec.ClearField(tag.FieldColor, field.TypeString)
}
if _u.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: label.GroupTable,
Columns: []string{label.GroupColumn},
Table: tag.GroupTable,
Columns: []string{tag.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
@@ -255,8 +255,8 @@ func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: label.GroupTable,
Columns: []string{label.GroupColumn},
Table: tag.GroupTable,
Columns: []string{tag.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
@@ -271,8 +271,8 @@ func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: false,
Table: label.ItemsTable,
Columns: label.ItemsPrimaryKey,
Table: tag.ItemsTable,
Columns: tag.ItemsPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
@@ -284,8 +284,8 @@ func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: false,
Table: label.ItemsTable,
Columns: label.ItemsPrimaryKey,
Table: tag.ItemsTable,
Columns: tag.ItemsPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
@@ -300,8 +300,8 @@ func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: false,
Table: label.ItemsTable,
Columns: label.ItemsPrimaryKey,
Table: tag.ItemsTable,
Columns: tag.ItemsPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
@@ -314,7 +314,7 @@ func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{label.Label}
err = &NotFoundError{tag.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
@@ -324,28 +324,28 @@ func (_u *LabelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
return _node, nil
}
// LabelUpdateOne is the builder for updating a single Label entity.
type LabelUpdateOne struct {
// TagUpdateOne is the builder for updating a single Tag entity.
type TagUpdateOne struct {
config
fields []string
hooks []Hook
mutation *LabelMutation
mutation *TagMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *LabelUpdateOne) SetUpdatedAt(v time.Time) *LabelUpdateOne {
func (_u *TagUpdateOne) SetUpdatedAt(v time.Time) *TagUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetName sets the "name" field.
func (_u *LabelUpdateOne) SetName(v string) *LabelUpdateOne {
func (_u *TagUpdateOne) SetName(v string) *TagUpdateOne {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *LabelUpdateOne) SetNillableName(v *string) *LabelUpdateOne {
func (_u *TagUpdateOne) SetNillableName(v *string) *TagUpdateOne {
if v != nil {
_u.SetName(*v)
}
@@ -353,13 +353,13 @@ func (_u *LabelUpdateOne) SetNillableName(v *string) *LabelUpdateOne {
}
// SetDescription sets the "description" field.
func (_u *LabelUpdateOne) SetDescription(v string) *LabelUpdateOne {
func (_u *TagUpdateOne) SetDescription(v string) *TagUpdateOne {
_u.mutation.SetDescription(v)
return _u
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_u *LabelUpdateOne) SetNillableDescription(v *string) *LabelUpdateOne {
func (_u *TagUpdateOne) SetNillableDescription(v *string) *TagUpdateOne {
if v != nil {
_u.SetDescription(*v)
}
@@ -367,19 +367,19 @@ func (_u *LabelUpdateOne) SetNillableDescription(v *string) *LabelUpdateOne {
}
// ClearDescription clears the value of the "description" field.
func (_u *LabelUpdateOne) ClearDescription() *LabelUpdateOne {
func (_u *TagUpdateOne) ClearDescription() *TagUpdateOne {
_u.mutation.ClearDescription()
return _u
}
// SetColor sets the "color" field.
func (_u *LabelUpdateOne) SetColor(v string) *LabelUpdateOne {
func (_u *TagUpdateOne) SetColor(v string) *TagUpdateOne {
_u.mutation.SetColor(v)
return _u
}
// SetNillableColor sets the "color" field if the given value is not nil.
func (_u *LabelUpdateOne) SetNillableColor(v *string) *LabelUpdateOne {
func (_u *TagUpdateOne) SetNillableColor(v *string) *TagUpdateOne {
if v != nil {
_u.SetColor(*v)
}
@@ -387,30 +387,30 @@ func (_u *LabelUpdateOne) SetNillableColor(v *string) *LabelUpdateOne {
}
// ClearColor clears the value of the "color" field.
func (_u *LabelUpdateOne) ClearColor() *LabelUpdateOne {
func (_u *TagUpdateOne) ClearColor() *TagUpdateOne {
_u.mutation.ClearColor()
return _u
}
// SetGroupID sets the "group" edge to the Group entity by ID.
func (_u *LabelUpdateOne) SetGroupID(id uuid.UUID) *LabelUpdateOne {
func (_u *TagUpdateOne) SetGroupID(id uuid.UUID) *TagUpdateOne {
_u.mutation.SetGroupID(id)
return _u
}
// SetGroup sets the "group" edge to the Group entity.
func (_u *LabelUpdateOne) SetGroup(v *Group) *LabelUpdateOne {
func (_u *TagUpdateOne) SetGroup(v *Group) *TagUpdateOne {
return _u.SetGroupID(v.ID)
}
// AddItemIDs adds the "items" edge to the Item entity by IDs.
func (_u *LabelUpdateOne) AddItemIDs(ids ...uuid.UUID) *LabelUpdateOne {
func (_u *TagUpdateOne) AddItemIDs(ids ...uuid.UUID) *TagUpdateOne {
_u.mutation.AddItemIDs(ids...)
return _u
}
// AddItems adds the "items" edges to the Item entity.
func (_u *LabelUpdateOne) AddItems(v ...*Item) *LabelUpdateOne {
func (_u *TagUpdateOne) AddItems(v ...*Item) *TagUpdateOne {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
@@ -418,31 +418,31 @@ func (_u *LabelUpdateOne) AddItems(v ...*Item) *LabelUpdateOne {
return _u.AddItemIDs(ids...)
}
// Mutation returns the LabelMutation object of the builder.
func (_u *LabelUpdateOne) Mutation() *LabelMutation {
// Mutation returns the TagMutation object of the builder.
func (_u *TagUpdateOne) Mutation() *TagMutation {
return _u.mutation
}
// ClearGroup clears the "group" edge to the Group entity.
func (_u *LabelUpdateOne) ClearGroup() *LabelUpdateOne {
func (_u *TagUpdateOne) ClearGroup() *TagUpdateOne {
_u.mutation.ClearGroup()
return _u
}
// ClearItems clears all "items" edges to the Item entity.
func (_u *LabelUpdateOne) ClearItems() *LabelUpdateOne {
func (_u *TagUpdateOne) ClearItems() *TagUpdateOne {
_u.mutation.ClearItems()
return _u
}
// RemoveItemIDs removes the "items" edge to Item entities by IDs.
func (_u *LabelUpdateOne) RemoveItemIDs(ids ...uuid.UUID) *LabelUpdateOne {
func (_u *TagUpdateOne) RemoveItemIDs(ids ...uuid.UUID) *TagUpdateOne {
_u.mutation.RemoveItemIDs(ids...)
return _u
}
// RemoveItems removes "items" edges to Item entities.
func (_u *LabelUpdateOne) RemoveItems(v ...*Item) *LabelUpdateOne {
func (_u *TagUpdateOne) RemoveItems(v ...*Item) *TagUpdateOne {
ids := make([]uuid.UUID, len(v))
for i := range v {
ids[i] = v[i].ID
@@ -450,27 +450,27 @@ func (_u *LabelUpdateOne) RemoveItems(v ...*Item) *LabelUpdateOne {
return _u.RemoveItemIDs(ids...)
}
// Where appends a list predicates to the LabelUpdate builder.
func (_u *LabelUpdateOne) Where(ps ...predicate.Label) *LabelUpdateOne {
// Where appends a list predicates to the TagUpdate builder.
func (_u *TagUpdateOne) Where(ps ...predicate.Tag) *TagUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *LabelUpdateOne) Select(field string, fields ...string) *LabelUpdateOne {
func (_u *TagUpdateOne) Select(field string, fields ...string) *TagUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated Label entity.
func (_u *LabelUpdateOne) Save(ctx context.Context) (*Label, error) {
// Save executes the query and returns the updated Tag entity.
func (_u *TagUpdateOne) Save(ctx context.Context) (*Tag, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *LabelUpdateOne) SaveX(ctx context.Context) *Label {
func (_u *TagUpdateOne) SaveX(ctx context.Context) *Tag {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
@@ -479,67 +479,67 @@ func (_u *LabelUpdateOne) SaveX(ctx context.Context) *Label {
}
// Exec executes the query on the entity.
func (_u *LabelUpdateOne) Exec(ctx context.Context) error {
func (_u *TagUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *LabelUpdateOne) ExecX(ctx context.Context) {
func (_u *TagUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *LabelUpdateOne) defaults() {
func (_u *TagUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := label.UpdateDefaultUpdatedAt()
v := tag.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *LabelUpdateOne) check() error {
func (_u *TagUpdateOne) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := label.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Label.name": %w`, err)}
if err := tag.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Tag.name": %w`, err)}
}
}
if v, ok := _u.mutation.Description(); ok {
if err := label.DescriptionValidator(v); err != nil {
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Label.description": %w`, err)}
if err := tag.DescriptionValidator(v); err != nil {
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "Tag.description": %w`, err)}
}
}
if v, ok := _u.mutation.Color(); ok {
if err := label.ColorValidator(v); err != nil {
return &ValidationError{Name: "color", err: fmt.Errorf(`ent: validator failed for field "Label.color": %w`, err)}
if err := tag.ColorValidator(v); err != nil {
return &ValidationError{Name: "color", err: fmt.Errorf(`ent: validator failed for field "Tag.color": %w`, err)}
}
}
if _u.mutation.GroupCleared() && len(_u.mutation.GroupIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "Label.group"`)
return errors.New(`ent: clearing a required unique edge "Tag.group"`)
}
return nil
}
func (_u *LabelUpdateOne) sqlSave(ctx context.Context) (_node *Label, err error) {
func (_u *TagUpdateOne) sqlSave(ctx context.Context) (_node *Tag, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(label.Table, label.Columns, sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID))
_spec := sqlgraph.NewUpdateSpec(tag.Table, tag.Columns, sqlgraph.NewFieldSpec(tag.FieldID, field.TypeUUID))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Label.id" for update`)}
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Tag.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, label.FieldID)
_spec.Node.Columns = append(_spec.Node.Columns, tag.FieldID)
for _, f := range fields {
if !label.ValidColumn(f) {
if !tag.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != label.FieldID {
if f != tag.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
@@ -552,29 +552,29 @@ func (_u *LabelUpdateOne) sqlSave(ctx context.Context) (_node *Label, err error)
}
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(label.FieldUpdatedAt, field.TypeTime, value)
_spec.SetField(tag.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(label.FieldName, field.TypeString, value)
_spec.SetField(tag.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Description(); ok {
_spec.SetField(label.FieldDescription, field.TypeString, value)
_spec.SetField(tag.FieldDescription, field.TypeString, value)
}
if _u.mutation.DescriptionCleared() {
_spec.ClearField(label.FieldDescription, field.TypeString)
_spec.ClearField(tag.FieldDescription, field.TypeString)
}
if value, ok := _u.mutation.Color(); ok {
_spec.SetField(label.FieldColor, field.TypeString, value)
_spec.SetField(tag.FieldColor, field.TypeString, value)
}
if _u.mutation.ColorCleared() {
_spec.ClearField(label.FieldColor, field.TypeString)
_spec.ClearField(tag.FieldColor, field.TypeString)
}
if _u.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: label.GroupTable,
Columns: []string{label.GroupColumn},
Table: tag.GroupTable,
Columns: []string{tag.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
@@ -586,8 +586,8 @@ func (_u *LabelUpdateOne) sqlSave(ctx context.Context) (_node *Label, err error)
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: label.GroupTable,
Columns: []string{label.GroupColumn},
Table: tag.GroupTable,
Columns: []string{tag.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
@@ -602,8 +602,8 @@ func (_u *LabelUpdateOne) sqlSave(ctx context.Context) (_node *Label, err error)
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: false,
Table: label.ItemsTable,
Columns: label.ItemsPrimaryKey,
Table: tag.ItemsTable,
Columns: tag.ItemsPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
@@ -615,8 +615,8 @@ func (_u *LabelUpdateOne) sqlSave(ctx context.Context) (_node *Label, err error)
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: false,
Table: label.ItemsTable,
Columns: label.ItemsPrimaryKey,
Table: tag.ItemsTable,
Columns: tag.ItemsPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
@@ -631,8 +631,8 @@ func (_u *LabelUpdateOne) sqlSave(ctx context.Context) (_node *Label, err error)
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
Inverse: false,
Table: label.ItemsTable,
Columns: label.ItemsPrimaryKey,
Table: tag.ItemsTable,
Columns: tag.ItemsPrimaryKey,
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
@@ -643,12 +643,12 @@ func (_u *LabelUpdateOne) sqlSave(ctx context.Context) (_node *Label, err error)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Label{config: _u.config}
_node = &Tag{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{label.Label}
err = &NotFoundError{tag.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}

View File

@@ -28,14 +28,14 @@ type Tx struct {
ItemField *ItemFieldClient
// ItemTemplate is the client for interacting with the ItemTemplate builders.
ItemTemplate *ItemTemplateClient
// Label is the client for interacting with the Label builders.
Label *LabelClient
// Location is the client for interacting with the Location builders.
Location *LocationClient
// MaintenanceEntry is the client for interacting with the MaintenanceEntry builders.
MaintenanceEntry *MaintenanceEntryClient
// Notifier is the client for interacting with the Notifier builders.
Notifier *NotifierClient
// Tag is the client for interacting with the Tag builders.
Tag *TagClient
// TemplateField is the client for interacting with the TemplateField builders.
TemplateField *TemplateFieldClient
// User is the client for interacting with the User builders.
@@ -179,10 +179,10 @@ func (tx *Tx) init() {
tx.Item = NewItemClient(tx.config)
tx.ItemField = NewItemFieldClient(tx.config)
tx.ItemTemplate = NewItemTemplateClient(tx.config)
tx.Label = NewLabelClient(tx.config)
tx.Location = NewLocationClient(tx.config)
tx.MaintenanceEntry = NewMaintenanceEntryClient(tx.config)
tx.Notifier = NewNotifierClient(tx.config)
tx.Tag = NewTagClient(tx.config)
tx.TemplateField = NewTemplateFieldClient(tx.config)
tx.User = NewUserClient(tx.config)
}

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/rs/zerolog/log"
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
)
//go:embed all:postgres
@@ -21,9 +22,9 @@ var sqliteFiles embed.FS
// embedded file system containing the migration files for the specified dialect.
func Migrations(dialect string) (embed.FS, error) {
switch dialect {
case "postgres":
case config.DriverPostgres:
return postgresFiles, nil
case "sqlite3":
case config.DriverSqlite3:
return sqliteFiles, nil
default:
log.Error().Str("dialect", dialect).Msg("unknown sql dialect")

View File

@@ -0,0 +1,6 @@
-- +goose Up
ALTER TABLE labels RENAME TO tags;
ALTER TABLE label_items RENAME TO tag_items;
ALTER TABLE tag_items RENAME COLUMN label_id TO tag_id;
ALTER TABLE tags RENAME COLUMN group_labels TO group_tags;
ALTER TABLE item_templates RENAME COLUMN default_label_ids TO default_tag_ids;

View File

@@ -1,5 +1,6 @@
-- +goose Up
-- +goose StatementBegin
-- +goose no transaction
PRAGMA foreign_keys=OFF;
-- SQLite doesn't support ALTER COLUMN directly, so we need to recreate the table
-- Create a temporary table with the new schema
CREATE TABLE users_temp (
@@ -21,7 +22,7 @@ CREATE TABLE users_temp (
);
-- Copy data from the original table
INSERT INTO users_temp SELECT * FROM users;
INSERT INTO users_temp SELECT id, created_at, updated_at, name, email, password, is_superuser, superuser, role, activated_on, group_users FROM users;
-- Drop the original table
DROP TABLE users;
@@ -31,38 +32,4 @@ ALTER TABLE users_temp RENAME TO users;
-- Recreate the unique index
CREATE UNIQUE INDEX users_email_key on users (email);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
-- Create the original table structure
CREATE TABLE users_temp (
id uuid not null
primary key,
created_at datetime not null,
updated_at datetime not null,
name text not null,
email text not null,
password text not null,
is_superuser bool default false not null,
superuser bool default false not null,
role text default 'user' not null,
activated_on datetime,
group_users uuid not null
constraint users_groups_users
references groups
on delete cascade
);
-- Copy data from the current table (this will fail if there are NULL passwords)
INSERT INTO users_temp SELECT * FROM users;
-- Drop the current table
DROP TABLE users;
-- Rename the temporary table
ALTER TABLE users_temp RENAME TO users;
-- Recreate the unique index
CREATE UNIQUE INDEX users_email_key on users (email);
-- +goose StatementEnd
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,4 @@
-- +goose Up
-- Force the role and superuser flags, previous nullable password migration (prior to v0.22.2)
-- caused them to flip-flop during migration for some users.
UPDATE users SET role = 'owner', is_superuser = 0, superuser = 0;

View File

@@ -0,0 +1,100 @@
-- +goose Up
-- +goose no transaction
PRAGMA foreign_keys=off;
CREATE TABLE IF NOT EXISTS tags (
id uuid not null
primary key,
created_at datetime not null,
updated_at datetime not null,
name text not null,
description text,
color text,
group_tags uuid not null
constraint tags_groups_tags
references groups
on delete cascade
);
INSERT INTO tags(id, created_at, updated_at, name, description, color, group_tags)
SELECT id, created_at, updated_at, name, description, color, group_labels FROM labels;
DROP TABLE labels;
CREATE TABLE IF NOT EXISTS tag_items (
tag_id uuid not null
constraint tag_items_tag_id
references tags
on delete cascade,
item_id uuid not null
constraint tag_items_item_id
references items
on delete cascade,
primary key (tag_id, item_id)
);
INSERT INTO tag_items(tag_id, item_id)
SELECT label_id, item_id FROM label_items;
DROP TABLE IF EXISTS label_items;
CREATE TABLE IF NOT EXISTS item_templates_temp
(
id uuid not null
primary key,
created_at datetime not null,
updated_at datetime not null,
name text not null,
description text,
notes text,
default_quantity integer default 1 not null,
default_insured bool default false not null,
default_name text,
default_description text,
default_manufacturer text,
default_model_number text,
default_lifetime_warranty bool default false not null,
default_warranty_details text,
include_warranty_fields bool default false not null,
include_purchase_fields bool default false not null,
include_sold_fields bool default false not null,
default_tag_ids json,
item_template_location uuid
references locations(id)
on delete set null,
group_item_templates uuid not null
constraint item_templates_groups_item_templates
references groups
on delete cascade
);
CREATE TABLE IF NOT EXISTS template_fields_temp
(
id uuid not null
primary key,
created_at datetime not null,
updated_at datetime not null,
name text not null,
description text,
type text not null,
text_value text,
item_template_fields uuid
constraint template_fields_item_templates_fields
references item_templates_temp
on delete cascade
);
INSERT INTO item_templates_temp(id, created_at, updated_at, name, description, notes, default_quantity, default_insured, default_name, default_description, default_manufacturer, default_model_number, default_lifetime_warranty, default_warranty_details, include_warranty_fields, include_purchase_fields, include_sold_fields, default_tag_ids, item_template_location, group_item_templates)
SELECT id, created_at, updated_at, name, description, notes, default_quantity, default_insured, default_name, default_description, default_manufacturer, default_model_number, default_lifetime_warranty, default_warranty_details, include_warranty_fields, include_purchase_fields, include_sold_fields, default_label_ids, item_template_location, group_item_templates FROM item_templates;
INSERT INTO template_fields_temp(id, created_at, updated_at, name, description, type, text_value, item_template_fields)
SELECT id, created_at, updated_at, name, description, type, text_value, item_template_fields FROM template_fields;
DROP TABLE IF EXISTS template_fields;
DROP TABLE IF EXISTS item_templates;
ALTER TABLE item_templates_temp RENAME TO item_templates;
ALTER TABLE template_fields_temp RENAME TO template_fields;
PRAGMA foreign_keys=on;

View File

@@ -11,7 +11,7 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
)
@@ -79,7 +79,7 @@ type (
TotalUsers int `json:"totalUsers"`
TotalItems int `json:"totalItems"`
TotalLocations int `json:"totalLocations"`
TotalLabels int `json:"totalLabels"`
TotalTags int `json:"totalTags"`
TotalItemPrice float64 `json:"totalItemPrice"`
TotalWithWarranty int `json:"totalWithWarranty"`
}
@@ -131,21 +131,21 @@ func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, gid
return v, err
}
func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, gid uuid.UUID) ([]TotalsByOrganizer, error) {
func (r *GroupRepository) StatsTagsByPurchasePrice(ctx context.Context, gid uuid.UUID) ([]TotalsByOrganizer, error) {
var v []TotalsByOrganizer
err := r.db.Label.Query().
err := r.db.Tag.Query().
Where(
label.HasGroupWith(group.ID(gid)),
tag.HasGroupWith(group.ID(gid)),
).
GroupBy(label.FieldID, label.FieldName).
GroupBy(tag.FieldID, tag.FieldName).
Aggregate(func(sq *sql.Selector) string {
itemTable := sql.Table(item.Table)
jt := sql.Table(label.ItemsTable)
jt := sql.Table(tag.ItemsTable)
sq.Join(jt).On(sq.C(label.FieldID), jt.C(label.ItemsPrimaryKey[0]))
sq.Join(itemTable).On(jt.C(label.ItemsPrimaryKey[1]), itemTable.C(item.FieldID))
sq.Join(jt).On(sq.C(tag.FieldID), jt.C(tag.ItemsPrimaryKey[0]))
sq.Join(itemTable).On(jt.C(tag.ItemsPrimaryKey[1]), itemTable.C(item.FieldID))
return sql.As(sql.Sum(itemTable.C(item.FieldPurchasePrice)), "total")
}).
@@ -226,7 +226,7 @@ func (r *GroupRepository) StatsGroup(ctx context.Context, gid uuid.UUID) (GroupS
(SELECT COUNT(*) FROM users WHERE group_users = $2) AS total_users,
(SELECT COUNT(*) FROM items WHERE group_items = $2 AND items.archived = false) AS total_items,
(SELECT COUNT(*) FROM locations WHERE group_locations = $2) AS total_locations,
(SELECT COUNT(*) FROM labels WHERE group_labels = $2) AS total_labels,
(SELECT COUNT(*) FROM tags WHERE group_tags = $2) AS total_tags,
(SELECT SUM(purchase_price*quantity) FROM items WHERE group_items = $2 AND items.archived = false) AS total_item_price,
(SELECT COUNT(*)
FROM items
@@ -241,7 +241,7 @@ func (r *GroupRepository) StatsGroup(ctx context.Context, gid uuid.UUID) (GroupS
var maybeTotalItemPrice *float64
var maybeTotalWithWarranty *int
err := row.Scan(&stats.TotalUsers, &stats.TotalItems, &stats.TotalLocations, &stats.TotalLabels, &maybeTotalItemPrice, &maybeTotalWithWarranty)
err := row.Scan(&stats.TotalUsers, &stats.TotalItems, &stats.TotalLocations, &stats.TotalTags, &maybeTotalItemPrice, &maybeTotalWithWarranty)
if err != nil {
return GroupStatistics{}, err
}

View File

@@ -36,13 +36,13 @@ func Test_Group_Update(t *testing.T) {
// TODO: Fix this test at some point, the data itself in production/development is working fine, it only fails on the test
/*func Test_Group_GroupStatistics(t *testing.T) {
useItems(t, 20)
useLabels(t, 20)
useTags(t, 20)
stats, err := tRepos.Groups.StatsGroup(context.Background(), tGroup.ID)
require.NoError(t, err)
assert.Equal(t, 20, stats.TotalItems)
assert.Equal(t, 20, stats.TotalLabels)
assert.Equal(t, 20, stats.TotalTags)
assert.Equal(t, 1, stats.TotalUsers)
assert.Equal(t, 1, stats.TotalLocations)
}*/

View File

@@ -10,7 +10,7 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/templatefield"
)
@@ -27,7 +27,7 @@ type (
TextValue string `json:"textValue"`
}
TemplateLabelSummary struct {
TemplateTagSummary struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
}
@@ -52,9 +52,9 @@ type (
DefaultLifetimeWarranty bool `json:"defaultLifetimeWarranty"`
DefaultWarrantyDetails *string `json:"defaultWarrantyDetails,omitempty" extensions:"x-nullable" validate:"omitempty,max=1000"`
// Default location and labels
// Default location and tags
DefaultLocationID uuid.UUID `json:"defaultLocationId,omitempty" extensions:"x-nullable"`
DefaultLabelIDs *[]uuid.UUID `json:"defaultLabelIds,omitempty" extensions:"x-nullable"`
DefaultTagIDs *[]uuid.UUID `json:"defaultTagIds,omitempty" extensions:"x-nullable"`
// Metadata flags
IncludeWarrantyFields bool `json:"includeWarrantyFields"`
@@ -81,9 +81,9 @@ type (
DefaultLifetimeWarranty bool `json:"defaultLifetimeWarranty"`
DefaultWarrantyDetails *string `json:"defaultWarrantyDetails,omitempty" extensions:"x-nullable" validate:"omitempty,max=1000"`
// Default location and labels
// Default location and tags
DefaultLocationID uuid.UUID `json:"defaultLocationId,omitempty" extensions:"x-nullable"`
DefaultLabelIDs *[]uuid.UUID `json:"defaultLabelIds,omitempty" extensions:"x-nullable"`
DefaultTagIDs *[]uuid.UUID `json:"defaultTagIds,omitempty" extensions:"x-nullable"`
// Metadata flags
IncludeWarrantyFields bool `json:"includeWarrantyFields"`
@@ -120,9 +120,9 @@ type (
DefaultLifetimeWarranty bool `json:"defaultLifetimeWarranty"`
DefaultWarrantyDetails string `json:"defaultWarrantyDetails"`
// Default location and labels
// Default location and tags
DefaultLocation *TemplateLocationSummary `json:"defaultLocation"`
DefaultLabels []TemplateLabelSummary `json:"defaultLabels"`
DefaultTags []TemplateTagSummary `json:"defaultTags"`
// Metadata flags
IncludeWarrantyFields bool `json:"includeWarrantyFields"`
@@ -177,15 +177,15 @@ func (r *ItemTemplatesRepository) mapTemplateOut(ctx context.Context, template *
}
}
// Fetch labels from database using stored IDs
labels := make([]TemplateLabelSummary, 0)
if len(template.DefaultLabelIds) > 0 {
labelEntities, err := r.db.Label.Query().
Where(label.IDIn(template.DefaultLabelIds...)).
// Fetch tags from database using stored IDs
tags := make([]TemplateTagSummary, 0)
if len(template.DefaultTagIds) > 0 {
tagEntities, err := r.db.Tag.Query().
Where(tag.IDIn(template.DefaultTagIds...)).
All(ctx)
if err == nil {
for _, l := range labelEntities {
labels = append(labels, TemplateLabelSummary{
for _, l := range tagEntities {
tags = append(tags, TemplateTagSummary{
ID: l.ID,
Name: l.Name,
})
@@ -209,7 +209,7 @@ func (r *ItemTemplatesRepository) mapTemplateOut(ctx context.Context, template *
DefaultLifetimeWarranty: template.DefaultLifetimeWarranty,
DefaultWarrantyDetails: template.DefaultWarrantyDetails,
DefaultLocation: location,
DefaultLabels: labels,
DefaultTags: tags,
IncludeWarrantyFields: template.IncludeWarrantyFields,
IncludePurchaseFields: template.IncludePurchaseFields,
IncludeSoldFields: template.IncludeSoldFields,
@@ -284,9 +284,9 @@ func (r *ItemTemplatesRepository) Create(ctx context.Context, gid uuid.UUID, dat
if data.DefaultLocationID != uuid.Nil {
q.SetLocationID(data.DefaultLocationID)
}
// Set default label IDs (stored as JSON)
if data.DefaultLabelIDs != nil && len(*data.DefaultLabelIDs) > 0 {
q.SetDefaultLabelIds(*data.DefaultLabelIDs)
// Set default tag IDs (stored as JSON)
if data.DefaultTagIDs != nil && len(*data.DefaultTagIDs) > 0 {
q.SetDefaultTagIds(*data.DefaultTagIDs)
}
template, err := q.Save(ctx)
@@ -351,11 +351,11 @@ func (r *ItemTemplatesRepository) Update(ctx context.Context, gid uuid.UUID, dat
updateQ.ClearLocation()
}
// Update default label IDs (stored as JSON)
if data.DefaultLabelIDs != nil && len(*data.DefaultLabelIDs) > 0 {
updateQ.SetDefaultLabelIds(*data.DefaultLabelIDs)
// Update default tag IDs (stored as JSON)
if data.DefaultTagIDs != nil && len(*data.DefaultTagIDs) > 0 {
updateQ.SetDefaultTagIds(*data.DefaultTagIDs)
} else {
updateQ.ClearDefaultLabelIds()
updateQ.ClearDefaultTagIds()
}
_, err = updateQ.Save(ctx)

View File

@@ -263,34 +263,34 @@ func TestItemTemplatesRepository_CreateWithLocation(t *testing.T) {
require.NoError(t, err)
}
func TestItemTemplatesRepository_CreateWithLabels(t *testing.T) {
// Create some labels
label1, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
func TestItemTemplatesRepository_CreateWithTags(t *testing.T) {
// Create some tags
tag1, err := tRepos.Tags.Create(context.Background(), tGroup.ID, TagCreate{
Name: fk.Str(10),
Description: fk.Str(50),
})
require.NoError(t, err)
label2, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
tag2, err := tRepos.Tags.Create(context.Background(), tGroup.ID, TagCreate{
Name: fk.Str(10),
Description: fk.Str(50),
})
require.NoError(t, err)
t.Cleanup(func() {
_ = tRepos.Labels.delete(context.Background(), label1.ID)
_ = tRepos.Labels.delete(context.Background(), label2.ID)
_ = tRepos.Tags.delete(context.Background(), tag1.ID)
_ = tRepos.Tags.delete(context.Background(), tag2.ID)
})
// Create template with labels
// Create template with tags
data := templateFactory()
labelIDs := []uuid.UUID{label1.ID, label2.ID}
data.DefaultLabelIDs = &labelIDs
tagIDs := []uuid.UUID{tag1.ID, tag2.ID}
data.DefaultTagIDs = &tagIDs
template, err := tRepos.ItemTemplates.Create(context.Background(), tGroup.ID, data)
require.NoError(t, err)
assert.Len(t, template.DefaultLabels, 2)
assert.Len(t, template.DefaultTags, 2)
// Cleanup
err = tRepos.ItemTemplates.Delete(context.Background(), tGroup.ID, template.ID)

View File

@@ -13,7 +13,7 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemfield"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
@@ -38,8 +38,8 @@ type (
Search string `json:"search"`
AssetID AssetID `json:"assetId"`
LocationIDs []uuid.UUID `json:"locationIds"`
LabelIDs []uuid.UUID `json:"labelIds"`
NegateLabels bool `json:"negateLabels"`
TagIDs []uuid.UUID `json:"tagIds"`
NegateTags bool `json:"negateTags"`
OnlyWithoutPhoto bool `json:"onlyWithoutPhoto"`
OnlyWithPhoto bool `json:"onlyWithPhoto"`
ParentItemIDs []uuid.UUID `json:"parentIds"`
@@ -76,7 +76,7 @@ type (
// Edges
LocationID uuid.UUID `json:"locationId"`
LabelIDs []uuid.UUID `json:"labelIds"`
TagIDs []uuid.UUID `json:"tagIds"`
}
ItemUpdate struct {
@@ -92,7 +92,7 @@ type (
// Edges
LocationID uuid.UUID `json:"locationId"`
LabelIDs []uuid.UUID `json:"labelIds"`
TagIDs []uuid.UUID `json:"tagIds"`
// Identifications
SerialNumber string `json:"serialNumber"`
@@ -125,7 +125,7 @@ type (
Quantity *int `json:"quantity,omitempty" extensions:"x-nullable,x-omitempty"`
ImportRef *string `json:"-,omitempty" extensions:"x-nullable,x-omitempty"`
LocationID uuid.UUID `json:"locationId" extensions:"x-nullable,x-omitempty"`
LabelIDs []uuid.UUID `json:"labelIds" extensions:"x-nullable,x-omitempty"`
TagIDs []uuid.UUID `json:"tagIds" extensions:"x-nullable,x-omitempty"`
}
ItemSummary struct {
@@ -144,7 +144,7 @@ type (
// Edges
Location *LocationSummary `json:"location,omitempty" extensions:"x-nullable,x-omitempty"`
Labels []LabelSummary `json:"labels"`
Tags []TagSummary `json:"tags"`
ImageID *uuid.UUID `json:"imageId,omitempty" extensions:"x-nullable,x-omitempty"`
ThumbnailId *uuid.UUID `json:"thumbnailId,omitempty" extensions:"x-nullable,x-omitempty"`
@@ -196,9 +196,9 @@ func mapItemSummary(item *ent.Item) ItemSummary {
location = &loc
}
labels := make([]LabelSummary, len(item.Edges.Label))
if item.Edges.Label != nil {
labels = mapEach(item.Edges.Label, mapLabelSummary)
tags := make([]TagSummary, len(item.Edges.Tag))
if item.Edges.Tag != nil {
tags = mapEach(item.Edges.Tag, mapTagSummary)
}
var imageID *uuid.UUID
@@ -235,7 +235,7 @@ func mapItemSummary(item *ent.Item) ItemSummary {
// Edges
Location: location,
Labels: labels,
Tags: tags,
// Warranty
Insured: item.Insured,
@@ -329,7 +329,7 @@ func (e *ItemsRepository) getOneTx(ctx context.Context, tx *ent.Tx, where ...pre
return mapItemOutErr(q.
WithFields().
WithLabel().
WithTag().
WithLocation().
WithGroup().
WithParent().
@@ -412,24 +412,24 @@ func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q Ite
// of filters is OR'd together.
//
// The goal is to allow matches like where the item has
// - one of the selected labels AND
// - one of the selected tags AND
// - one of the selected locations AND
// - one of the selected fields key/value matches
var andPredicates []predicate.Item
{
if len(q.LabelIDs) > 0 {
labelPredicates := make([]predicate.Item, 0, len(q.LabelIDs))
for _, l := range q.LabelIDs {
if !q.NegateLabels {
labelPredicates = append(labelPredicates, item.HasLabelWith(label.ID(l)))
if len(q.TagIDs) > 0 {
tagPredicates := make([]predicate.Item, 0, len(q.TagIDs))
for _, l := range q.TagIDs {
if !q.NegateTags {
tagPredicates = append(tagPredicates, item.HasTagWith(tag.ID(l)))
} else {
labelPredicates = append(labelPredicates, item.Not(item.HasLabelWith(label.ID(l))))
tagPredicates = append(tagPredicates, item.Not(item.HasTagWith(tag.ID(l))))
}
}
if !q.NegateLabels {
andPredicates = append(andPredicates, item.Or(labelPredicates...))
if !q.NegateTags {
andPredicates = append(andPredicates, item.Or(tagPredicates...))
} else {
andPredicates = append(andPredicates, item.And(labelPredicates...))
andPredicates = append(andPredicates, item.And(tagPredicates...))
}
}
@@ -504,7 +504,7 @@ func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q Ite
}
qb = qb.
WithLabel().
WithTag().
WithLocation().
WithAttachments(func(aq *ent.AttachmentQuery) {
aq.Where(
@@ -549,7 +549,7 @@ func (e *ItemsRepository) QueryByAssetID(ctx context.Context, gid uuid.UUID, ass
items, err := mapItemsSummaryErr(
qb.Order(ent.Asc(item.FieldName)).
WithLabel().
WithTag().
WithLocation().
All(ctx),
)
@@ -565,11 +565,11 @@ func (e *ItemsRepository) QueryByAssetID(ctx context.Context, gid uuid.UUID, ass
}, nil
}
// GetAll returns all the items in the database with the Labels and Locations eager loaded.
// GetAll returns all the items in the database with the Tags and Locations eager loaded.
func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemOut, error) {
return mapItemsOutErr(e.db.Item.Query().
Where(item.HasGroupWith(group.ID(gid))).
WithLabel().
WithTag().
WithLocation().
WithFields().
All(ctx))
@@ -641,8 +641,8 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCr
q.SetParentID(data.ParentID)
}
if len(data.LabelIDs) > 0 {
q.AddLabelIDs(data.LabelIDs...)
if len(data.TagIDs) > 0 {
q.AddTagIDs(data.TagIDs...)
}
result, err := q.Save(ctx)
@@ -660,7 +660,7 @@ type ItemCreateFromTemplate struct {
Description string
Quantity int
LocationID uuid.UUID
LabelIDs []uuid.UUID
TagIDs []uuid.UUID
Insured bool
Manufacturer string
ModelNumber string
@@ -707,8 +707,8 @@ func (e *ItemsRepository) CreateFromTemplate(ctx context.Context, gid uuid.UUID,
SetLifetimeWarranty(data.LifetimeWarranty).
SetWarrantyDetails(data.WarrantyDetails)
if len(data.LabelIDs) > 0 {
itemBuilder.AddLabelIDs(data.LabelIDs...)
if len(data.TagIDs) > 0 {
itemBuilder.AddTagIDs(data.TagIDs...)
}
_, err = itemBuilder.Save(ctx)
@@ -809,6 +809,88 @@ func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID)
return err
}
func (e *ItemsRepository) WipeInventory(ctx context.Context, gid uuid.UUID, wipeTags bool, wipeLocations bool, wipeMaintenance bool) (int, error) {
deleted := 0
// Wipe maintenance records if requested
// IMPORTANT: Must delete maintenance records BEFORE items since they are linked to items
if wipeMaintenance {
maintenanceCount, err := e.db.MaintenanceEntry.Delete().
Where(maintenanceentry.HasItemWith(item.HasGroupWith(group.ID(gid)))).
Exec(ctx)
if err != nil {
log.Err(err).Msg("failed to delete maintenance entries during wipe inventory")
} else {
log.Info().Int("count", maintenanceCount).Msg("deleted maintenance entries during wipe inventory")
deleted += maintenanceCount
}
}
// Get all items for the group
items, err := e.db.Item.Query().
Where(item.HasGroupWith(group.ID(gid))).
WithAttachments().
All(ctx)
if err != nil {
return 0, err
}
// Delete each item with its attachments
// Note: We manually delete attachments and items instead of calling DeleteByGroup
// to continue processing remaining items even if some deletions fail
for _, itm := range items {
// Delete all attachments first
for _, att := range itm.Edges.Attachments {
err := e.attachments.Delete(ctx, gid, itm.ID, att.ID)
if err != nil {
log.Err(err).Str("attachment_id", att.ID.String()).Msg("failed to delete attachment during wipe inventory")
// Continue with other attachments even if one fails
}
}
// Delete the item
_, err = e.db.Item.
Delete().
Where(
item.ID(itm.ID),
item.HasGroupWith(group.ID(gid)),
).Exec(ctx)
if err != nil {
log.Err(err).Str("item_id", itm.ID.String()).Msg("failed to delete item during wipe inventory")
// Skip to next item without incrementing counter
continue
}
// Only increment counter if deletion succeeded
deleted++
}
// Wipe tags if requested
if wipeTags {
tagCount, err := e.db.Tag.Delete().Where(tag.HasGroupWith(group.ID(gid))).Exec(ctx)
if err != nil {
log.Err(err).Msg("failed to delete tags during wipe inventory")
} else {
log.Info().Int("count", tagCount).Msg("deleted tags during wipe inventory")
deleted += tagCount
}
}
// Wipe locations if requested
if wipeLocations {
locationCount, err := e.db.Location.Delete().Where(location.HasGroupWith(group.ID(gid))).Exec(ctx)
if err != nil {
log.Err(err).Msg("failed to delete locations during wipe inventory")
} else {
log.Info().Int("count", locationCount).Msg("deleted locations during wipe inventory")
deleted += locationCount
}
}
e.publishMutationEvent(gid)
return deleted, nil
}
func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data ItemUpdate) (ItemOut, error) {
q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(gid))).
SetName(data.Name).
@@ -834,23 +916,23 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data
SetAssetID(int(data.AssetID)).
SetSyncChildItemsLocations(data.SyncChildItemsLocations)
currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx)
currentTags, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryTag().All(ctx)
if err != nil {
return ItemOut{}, err
}
set := newIDSet(currentLabels)
set := newIDSet(currentTags)
for _, l := range data.LabelIDs {
for _, l := range data.TagIDs {
if set.Contains(l) {
set.Remove(l)
continue
}
q.AddLabelIDs(l)
q.AddTagIDs(l)
}
if set.Len() > 0 {
q.RemoveLabelIDs(set.Slice()...)
q.RemoveTagIDs(set.Slice()...)
}
if data.ParentID != uuid.Nil {
@@ -1005,26 +1087,26 @@ func (e *ItemsRepository) Patch(ctx context.Context, gid, id uuid.UUID, data Ite
return err
}
if data.LabelIDs != nil {
currentLabels, err := tx.Item.Query().Where(item.ID(id), item.HasGroupWith(group.ID(gid))).QueryLabel().All(ctx)
if data.TagIDs != nil {
currentTags, err := tx.Item.Query().Where(item.ID(id), item.HasGroupWith(group.ID(gid))).QueryTag().All(ctx)
if err != nil {
return err
}
set := newIDSet(currentLabels)
set := newIDSet(currentTags)
addLabels := []uuid.UUID{}
for _, l := range data.LabelIDs {
addTags := []uuid.UUID{}
for _, l := range data.TagIDs {
if set.Contains(l) {
set.Remove(l)
} else {
addLabels = append(addLabels, l)
addTags = append(addTags, l)
}
}
if len(addLabels) > 0 {
if len(addTags) > 0 {
if err := tx.Item.Update().
Where(item.ID(id), item.HasGroupWith(group.ID(gid))).
AddLabelIDs(addLabels...).
AddTagIDs(addTags...).
Exec(ctx); err != nil {
return err
}
@@ -1032,7 +1114,7 @@ func (e *ItemsRepository) Patch(ctx context.Context, gid, id uuid.UUID, data Ite
if set.Len() > 0 {
if err := tx.Item.Update().
Where(item.ID(id), item.HasGroupWith(group.ID(gid))).
RemoveLabelIDs(set.Slice()...).
RemoveTagIDs(set.Slice()...).
Exec(ctx); err != nil {
return err
}
@@ -1320,13 +1402,13 @@ func (e *ItemsRepository) Duplicate(ctx context.Context, gid, id uuid.UUID, opti
itemBuilder.SetParentID(originalItem.Parent.ID)
}
// Add labels
if len(originalItem.Labels) > 0 {
labelIDs := make([]uuid.UUID, len(originalItem.Labels))
for i, label := range originalItem.Labels {
labelIDs[i] = label.ID
// Add tags
if len(originalItem.Tags) > 0 {
tagIDs := make([]uuid.UUID, len(originalItem.Tags))
for i, tag := range originalItem.Tags {
tagIDs[i] = tag.ID
}
itemBuilder.AddLabelIDs(labelIDs...)
itemBuilder.AddTagIDs(tagIDs...)
}
_, err = itemBuilder.Save(ctx)

View File

@@ -165,14 +165,14 @@ func TestItemsRepository_Delete(t *testing.T) {
assert.Empty(t, results)
}
func TestItemsRepository_Update_Labels(t *testing.T) {
func TestItemsRepository_Update_Tags(t *testing.T) {
entity := useItems(t, 1)[0]
labels := useLabels(t, 3)
tags := useTags(t, 3)
labelsIDs := []uuid.UUID{labels[0].ID, labels[1].ID, labels[2].ID}
tagsIDs := []uuid.UUID{tags[0].ID, tags[1].ID, tags[2].ID}
type args struct {
labelIds []uuid.UUID
tagIds []uuid.UUID
}
tests := []struct {
@@ -181,44 +181,44 @@ func TestItemsRepository_Update_Labels(t *testing.T) {
want []uuid.UUID
}{
{
name: "add all labels",
name: "add all tags",
args: args{
labelIds: labelsIDs,
tagIds: tagsIDs,
},
want: labelsIDs,
want: tagsIDs,
},
{
name: "update with one label",
name: "update with one tag",
args: args{
labelIds: labelsIDs[:1],
tagIds: tagsIDs[:1],
},
want: labelsIDs[:1],
want: tagsIDs[:1],
},
{
name: "add one new label to existing single label",
name: "add one new tag to existing single tag",
args: args{
labelIds: labelsIDs[1:],
tagIds: tagsIDs[1:],
},
want: labelsIDs[1:],
want: tagsIDs[1:],
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Apply all labels to entity
// Apply all tags to entity
updateData := ItemUpdate{
ID: entity.ID,
Name: entity.Name,
LocationID: entity.Location.ID,
LabelIDs: tt.args.labelIds,
TagIDs: tt.args.tagIds,
}
updated, err := tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, updateData)
require.NoError(t, err)
assert.Len(t, tt.want, len(updated.Labels))
assert.Len(t, tt.want, len(updated.Tags))
for _, label := range updated.Labels {
assert.Contains(t, tt.want, label.ID)
for _, tag := range updated.Tags {
assert.Contains(t, tt.want, tag.ID)
}
})
}
@@ -234,7 +234,7 @@ func TestItemsRepository_Update(t *testing.T) {
Name: entity.Name,
LocationID: entity.Location.ID,
SerialNumber: fk.Str(10),
LabelIDs: nil,
TagIDs: nil,
ModelNumber: fk.Str(10),
Manufacturer: fk.Str(10),
PurchaseTime: types.DateFromTime(time.Now()),
@@ -398,4 +398,160 @@ func TestItemsRepository_DeleteByGroupWithAttachments(t *testing.T) {
require.Error(t, err)
}
func TestItemsRepository_WipeInventory(t *testing.T) {
// Create test data: items, tags, locations, and maintenance entries
// Create locations
loc1, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
Name: "Test Location 1",
Description: "Test location for wipe test",
})
require.NoError(t, err)
loc2, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
Name: "Test Location 2",
Description: "Another test location",
})
require.NoError(t, err)
// Create tags
tag1, err := tRepos.Tags.Create(context.Background(), tGroup.ID, TagCreate{
Name: "Test Tag 1",
Description: "Test tag for wipe test",
})
require.NoError(t, err)
tag2, err := tRepos.Tags.Create(context.Background(), tGroup.ID, TagCreate{
Name: "Test Tag 2",
Description: "Another test tag",
})
require.NoError(t, err)
// Create items
item1, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
Name: "Test Item 1",
Description: "Test item for wipe test",
LocationID: loc1.ID,
TagIDs: []uuid.UUID{tag1.ID},
})
require.NoError(t, err)
item2, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
Name: "Test Item 2",
Description: "Another test item",
LocationID: loc2.ID,
TagIDs: []uuid.UUID{tag2.ID},
})
require.NoError(t, err)
// Create maintenance entries for items
_, err = tRepos.MaintEntry.Create(context.Background(), item1.ID, MaintenanceEntryCreate{
CompletedDate: types.DateFromTime(time.Now()),
Name: "Test Maintenance 1",
Description: "Test maintenance entry",
Cost: 100.0,
})
require.NoError(t, err)
_, err = tRepos.MaintEntry.Create(context.Background(), item2.ID, MaintenanceEntryCreate{
CompletedDate: types.DateFromTime(time.Now()),
Name: "Test Maintenance 2",
Description: "Another test maintenance entry",
Cost: 200.0,
})
require.NoError(t, err)
// Test 1: Wipe inventory with all options enabled
t.Run("wipe all including tags, locations, and maintenance", func(t *testing.T) {
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, true, true, true)
require.NoError(t, err)
assert.Greater(t, deleted, 0, "Should have deleted at least some entities")
// Verify items are deleted
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item1.ID)
require.Error(t, err, "Item 1 should be deleted")
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item2.ID)
require.Error(t, err, "Item 2 should be deleted")
// Verify maintenance entries are deleted (query by item ID, should return empty)
maint1List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item1.ID, MaintenanceFilters{})
require.NoError(t, err)
assert.Empty(t, maint1List, "Maintenance entry 1 should be deleted")
maint2List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item2.ID, MaintenanceFilters{})
require.NoError(t, err)
assert.Empty(t, maint2List, "Maintenance entry 2 should be deleted")
// Verify tags are deleted
_, err = tRepos.Tags.GetOneByGroup(context.Background(), tGroup.ID, tag1.ID)
require.Error(t, err, "Tag 1 should be deleted")
_, err = tRepos.Tags.GetOneByGroup(context.Background(), tGroup.ID, tag2.ID)
require.Error(t, err, "Tag 2 should be deleted")
// Verify locations are deleted
_, err = tRepos.Locations.Get(context.Background(), loc1.ID)
require.Error(t, err, "Location 1 should be deleted")
_, err = tRepos.Locations.Get(context.Background(), loc2.ID)
require.Error(t, err, "Location 2 should be deleted")
})
}
func TestItemsRepository_WipeInventory_OnlyItems(t *testing.T) {
// Create test data
loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
Name: "Test Location",
Description: "Test location for wipe test",
})
require.NoError(t, err)
tag, err := tRepos.Tags.Create(context.Background(), tGroup.ID, TagCreate{
Name: "Test Tag",
Description: "Test tag for wipe test",
})
require.NoError(t, err)
item, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
Name: "Test Item",
Description: "Test item for wipe test",
LocationID: loc.ID,
TagIDs: []uuid.UUID{tag.ID},
})
require.NoError(t, err)
_, err = tRepos.MaintEntry.Create(context.Background(), item.ID, MaintenanceEntryCreate{
CompletedDate: types.DateFromTime(time.Now()),
Name: "Test Maintenance",
Description: "Test maintenance entry",
Cost: 100.0,
})
require.NoError(t, err)
// Test: Wipe inventory with only items (no tags, locations, or maintenance)
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, false, false, false)
require.NoError(t, err)
assert.Greater(t, deleted, 0, "Should have deleted at least the item")
// Verify item is deleted
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item.ID)
require.Error(t, err, "Item should be deleted")
// Verify maintenance entry is deleted due to cascade
maintList, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item.ID, MaintenanceFilters{})
require.NoError(t, err)
assert.Empty(t, maintList, "Maintenance entry should be cascade deleted with item")
// Verify tag still exists
_, err = tRepos.Tags.GetOneByGroup(context.Background(), tGroup.ID, tag.ID)
require.NoError(t, err, "Tag should still exist")
// Verify location still exists
_, err = tRepos.Locations.Get(context.Background(), loc.ID)
require.NoError(t, err, "Location should still exist")
// Cleanup
_ = tRepos.Tags.DeleteByGroup(context.Background(), tGroup.ID, tag.ID)
_ = tRepos.Locations.delete(context.Background(), loc.ID)
}

View File

@@ -8,30 +8,30 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/label"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/tag"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
)
type LabelRepository struct {
type TagRepository struct {
db *ent.Client
bus *eventbus.EventBus
}
type (
LabelCreate struct {
TagCreate struct {
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
Color string `json:"color"`
}
LabelUpdate struct {
TagUpdate struct {
ID uuid.UUID `json:"id"`
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
Color string `json:"color"`
}
LabelSummary struct {
TagSummary struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
@@ -40,86 +40,86 @@ type (
UpdatedAt time.Time `json:"updatedAt"`
}
LabelOut struct {
LabelSummary
TagOut struct {
TagSummary
}
)
func mapLabelSummary(label *ent.Label) LabelSummary {
return LabelSummary{
ID: label.ID,
Name: label.Name,
Description: label.Description,
Color: label.Color,
CreatedAt: label.CreatedAt,
UpdatedAt: label.UpdatedAt,
func mapTagSummary(tag *ent.Tag) TagSummary {
return TagSummary{
ID: tag.ID,
Name: tag.Name,
Description: tag.Description,
Color: tag.Color,
CreatedAt: tag.CreatedAt,
UpdatedAt: tag.UpdatedAt,
}
}
var (
mapLabelOutErr = mapTErrFunc(mapLabelOut)
mapLabelsOut = mapTEachErrFunc(mapLabelSummary)
mapTagOutErr = mapTErrFunc(mapTagOut)
mapTagsOut = mapTEachErrFunc(mapTagSummary)
)
func mapLabelOut(label *ent.Label) LabelOut {
return LabelOut{
LabelSummary: mapLabelSummary(label),
func mapTagOut(tag *ent.Tag) TagOut {
return TagOut{
TagSummary: mapTagSummary(tag),
}
}
func (r *LabelRepository) publishMutationEvent(gid uuid.UUID) {
func (r *TagRepository) publishMutationEvent(gid uuid.UUID) {
if r.bus != nil {
r.bus.Publish(eventbus.EventLabelMutation, eventbus.GroupMutationEvent{GID: gid})
r.bus.Publish(eventbus.EventTagMutation, eventbus.GroupMutationEvent{GID: gid})
}
}
func (r *LabelRepository) getOne(ctx context.Context, where ...predicate.Label) (LabelOut, error) {
return mapLabelOutErr(r.db.Label.Query().
func (r *TagRepository) getOne(ctx context.Context, where ...predicate.Tag) (TagOut, error) {
return mapTagOutErr(r.db.Tag.Query().
Where(where...).
WithGroup().
Only(ctx),
)
}
func (r *LabelRepository) GetOne(ctx context.Context, id uuid.UUID) (LabelOut, error) {
return r.getOne(ctx, label.ID(id))
func (r *TagRepository) GetOne(ctx context.Context, id uuid.UUID) (TagOut, error) {
return r.getOne(ctx, tag.ID(id))
}
func (r *LabelRepository) GetOneByGroup(ctx context.Context, gid, ld uuid.UUID) (LabelOut, error) {
return r.getOne(ctx, label.ID(ld), label.HasGroupWith(group.ID(gid)))
func (r *TagRepository) GetOneByGroup(ctx context.Context, gid, ld uuid.UUID) (TagOut, error) {
return r.getOne(ctx, tag.ID(ld), tag.HasGroupWith(group.ID(gid)))
}
func (r *LabelRepository) GetAll(ctx context.Context, groupID uuid.UUID) ([]LabelSummary, error) {
return mapLabelsOut(r.db.Label.Query().
Where(label.HasGroupWith(group.ID(groupID))).
Order(ent.Asc(label.FieldName)).
func (r *TagRepository) GetAll(ctx context.Context, groupID uuid.UUID) ([]TagSummary, error) {
return mapTagsOut(r.db.Tag.Query().
Where(tag.HasGroupWith(group.ID(groupID))).
Order(ent.Asc(tag.FieldName)).
WithGroup().
All(ctx),
)
}
func (r *LabelRepository) Create(ctx context.Context, groupID uuid.UUID, data LabelCreate) (LabelOut, error) {
label, err := r.db.Label.Create().
func (r *TagRepository) Create(ctx context.Context, groupID uuid.UUID, data TagCreate) (TagOut, error) {
tag, err := r.db.Tag.Create().
SetName(data.Name).
SetDescription(data.Description).
SetColor(data.Color).
SetGroupID(groupID).
Save(ctx)
if err != nil {
return LabelOut{}, err
return TagOut{}, err
}
label.Edges.Group = &ent.Group{ID: groupID} // bootstrap group ID
tag.Edges.Group = &ent.Group{ID: groupID} // bootstrap group ID
r.publishMutationEvent(groupID)
return mapLabelOut(label), err
return mapTagOut(tag), err
}
func (r *LabelRepository) update(ctx context.Context, data LabelUpdate, where ...predicate.Label) (int, error) {
func (r *TagRepository) update(ctx context.Context, data TagUpdate, where ...predicate.Tag) (int, error) {
if len(where) == 0 {
panic("empty where not supported empty")
}
return r.db.Label.Update().
return r.db.Tag.Update().
Where(where...).
SetName(data.Name).
SetDescription(data.Description).
@@ -127,27 +127,27 @@ func (r *LabelRepository) update(ctx context.Context, data LabelUpdate, where ..
Save(ctx)
}
func (r *LabelRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data LabelUpdate) (LabelOut, error) {
_, err := r.update(ctx, data, label.ID(data.ID), label.HasGroupWith(group.ID(gid)))
func (r *TagRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data TagUpdate) (TagOut, error) {
_, err := r.update(ctx, data, tag.ID(data.ID), tag.HasGroupWith(group.ID(gid)))
if err != nil {
return LabelOut{}, err
return TagOut{}, err
}
r.publishMutationEvent(gid)
return r.GetOne(ctx, data.ID)
}
// delete removes the label from the database. This should only be used when
// the label's ownership is already confirmed/validated.
func (r *LabelRepository) delete(ctx context.Context, id uuid.UUID) error {
return r.db.Label.DeleteOneID(id).Exec(ctx)
// delete removes the tag from the database. This should only be used when
// the tag's ownership is already confirmed/validated.
func (r *TagRepository) delete(ctx context.Context, id uuid.UUID) error {
return r.db.Tag.DeleteOneID(id).Exec(ctx)
}
func (r *LabelRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error {
_, err := r.db.Label.Delete().
func (r *TagRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error {
_, err := r.db.Tag.Delete().
Where(
label.ID(id),
label.HasGroupWith(group.ID(gid)),
tag.ID(id),
tag.HasGroupWith(group.ID(gid)),
).Exec(ctx)
if err != nil {
return err

Some files were not shown because too many files have changed in this diff Show More