Compare commits
169 Commits
mk/db-sett
...
copilot/au
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bc6b4519c | ||
|
|
6f77eae638 | ||
|
|
6926aabd62 | ||
|
|
b735ad12fd | ||
|
|
f3e817e139 | ||
|
|
153ecd1094 | ||
|
|
0be54da9cf | ||
|
|
e4aa38b264 | ||
|
|
e60f005990 | ||
|
|
7dfaa0298b | ||
|
|
fbe7382acd | ||
|
|
1003223b47 | ||
|
|
3c532896f5 | ||
|
|
4ba1a263c8 | ||
|
|
94f0123d9c | ||
|
|
1f6782f8be | ||
|
|
ec8703114f | ||
|
|
5cd7792701 | ||
|
|
d82c52df26 | ||
|
|
033c17552b | ||
|
|
2355438962 | ||
|
|
2a6773d1d6 | ||
|
|
c8c07e2878 | ||
|
|
a3c05c3497 | ||
|
|
ab0647fe68 | ||
|
|
0b616225a6 | ||
|
|
dc9c7b76f2 | ||
|
|
b99102e093 | ||
|
|
3077602f93 | ||
|
|
2bd6ff580a | ||
|
|
35941583c8 | ||
|
|
d576c89c7e | ||
|
|
ff355f3cd8 | ||
|
|
03dc7fa841 | ||
|
|
7aaaa346ab | ||
|
|
27309e61da | ||
|
|
61816acdaa | ||
|
|
c31410727b | ||
|
|
4557df86ed | ||
|
|
b8910f1b21 | ||
|
|
48e4f8da2a | ||
|
|
1e0158c27e | ||
|
|
4fb3ddd661 | ||
|
|
690005de06 | ||
|
|
23da976494 | ||
|
|
f0b8bb8b7f | ||
|
|
ecc9fa1959 | ||
|
|
7068a85dfb | ||
|
|
c73922c754 | ||
|
|
ae2179c01c | ||
|
|
09e056a3fb | ||
|
|
4abfc76865 | ||
|
|
aa48c958d7 | ||
|
|
2bd6d0a9e5 | ||
|
|
88275620f2 | ||
|
|
5a058250e6 | ||
|
|
afd7a10003 | ||
|
|
8eedd1e39d | ||
|
|
fedeb1a7e5 | ||
|
|
69b31a3be5 | ||
|
|
31d306ca05 | ||
|
|
1bfb716cea | ||
|
|
13b1524c56 | ||
|
|
b18599b6f4 | ||
|
|
473027c1ae | ||
|
|
3a77440996 | ||
|
|
731765c36c | ||
|
|
a86b1bd17b | ||
|
|
064b298968 | ||
|
|
2638f218f3 | ||
|
|
0f4f398b5a | ||
|
|
545993a8aa | ||
|
|
a1947dd09e | ||
|
|
018f1f5977 | ||
|
|
9a9e3d462e | ||
|
|
fc8b6f0dcf | ||
|
|
37890c2a22 | ||
|
|
096b682f0a | ||
|
|
e4d8bb2ada | ||
|
|
3becf046e6 | ||
|
|
a21b3257d4 | ||
|
|
5f9ab577bb | ||
|
|
0a969bb64d | ||
|
|
2d1d3d927b | ||
|
|
540028a22e | ||
|
|
14b0d51894 | ||
|
|
4334f926c0 | ||
|
|
1088972ff0 | ||
|
|
55e247ac71 | ||
|
|
05a2700718 | ||
|
|
06c11cdcd5 | ||
|
|
cc66330a74 | ||
|
|
3671ba2ba1 | ||
|
|
8898dd03f7 | ||
|
|
bd8708ce38 | ||
|
|
a0589b7629 | ||
|
|
0f4a686041 | ||
|
|
848b444aef | ||
|
|
e6e6056897 | ||
|
|
f36756d98e | ||
|
|
bfc5ffa76b | ||
|
|
1625354a70 | ||
|
|
d1016845a9 | ||
|
|
54ce340ac4 | ||
|
|
8c04ad7fe8 | ||
|
|
78d05bb155 | ||
|
|
3a648aa279 | ||
|
|
35a83c29af | ||
|
|
6697738342 | ||
|
|
a379e7c1ab | ||
|
|
e501d769da | ||
|
|
7d0e05dc5d | ||
|
|
81233e2999 | ||
|
|
415c3238ae | ||
|
|
b3153cc971 | ||
|
|
0801df9961 | ||
|
|
2bdd085289 | ||
|
|
c30cac4489 | ||
|
|
397a1c6f3e | ||
|
|
05a392346f | ||
|
|
28c3e102a2 | ||
|
|
116e39531b | ||
|
|
8a90b9c133 | ||
|
|
ef52009f57 | ||
|
|
76154263e0 | ||
|
|
108194e7fd | ||
|
|
bf845ae0f7 | ||
|
|
9be6a8c888 | ||
|
|
38a987676e | ||
|
|
1f746efe27 | ||
|
|
d57bf8834b | ||
|
|
cb2c58c3f4 | ||
|
|
7b3cf0453e | ||
|
|
825e72bceb | ||
|
|
8547fb9bb3 | ||
|
|
f66624774e | ||
|
|
33ec0c4aff | ||
|
|
6cd9e2779f | ||
|
|
a5d63ac4e1 | ||
|
|
ba45203ea3 | ||
|
|
609b7a606b | ||
|
|
b56505452f | ||
|
|
118bce4441 | ||
|
|
b535cdeb96 | ||
|
|
3b0e986f01 | ||
|
|
8f8dbf4a3a | ||
|
|
3183b38114 | ||
|
|
0408b1c03b | ||
|
|
a2e108eac4 | ||
|
|
227b81c6af | ||
|
|
3ef25d6463 | ||
|
|
d4e28e6f3b | ||
|
|
790352da34 | ||
|
|
52a6a31098 | ||
|
|
1d02285b0d | ||
|
|
19563d8b38 | ||
|
|
282977e82c | ||
|
|
769d5c5b95 | ||
|
|
b8f7ce7eb2 | ||
|
|
62ed3fabc2 | ||
|
|
304fc7f11f | ||
|
|
1b7a7a1999 | ||
|
|
a63f08ad87 | ||
|
|
9cb1a3f83c | ||
|
|
f86d38412b | ||
|
|
cbbe056d01 | ||
|
|
5f6b1a0805 | ||
|
|
27e9eb2277 | ||
|
|
6fcd10d796 |
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
backend/internal/data/ent/** linguist-generated=true
|
||||
backend/internal/data/ent/schema/** linguist-generated=false
|
||||
frontend/lib/api/types/** linguist-generated=true
|
||||
1
.github/FUNDING.yml
vendored
@@ -1 +1,2 @@
|
||||
open_collective: homebox
|
||||
github: [tankerkiller125,katosdev,tonyaellie]
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -58,6 +58,17 @@ body:
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: database
|
||||
attributes:
|
||||
label: Database Type
|
||||
description: What database backend are you using?
|
||||
multiple: false
|
||||
options:
|
||||
- SQLite
|
||||
- PostgreSQL
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: arch
|
||||
attributes:
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/internal.md
vendored
Normal 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]
|
||||
432
.github/instructions/backend-app-api-handlers.instructions.md
vendored
Normal 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`
|
||||
341
.github/instructions/backend-internal-core-services.instructions.md
vendored
Normal 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
|
||||
239
.github/instructions/backend-internal-data.instructions.md
vendored
Normal 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
|
||||
157
.github/instructions/code.instructions.md
vendored
Normal 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.
|
||||
480
.github/instructions/frontend.instructions.md
vendored
Normal 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
|
||||
BIN
.github/screenshots/1.png
vendored
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
.github/screenshots/10.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
.github/screenshots/2.png
vendored
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
.github/screenshots/3.png
vendored
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
.github/screenshots/4.png
vendored
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
.github/screenshots/5.png
vendored
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
.github/screenshots/6.png
vendored
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
.github/screenshots/7.png
vendored
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
.github/screenshots/8.png
vendored
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
.github/screenshots/9.png
vendored
Normal file
|
After Width: | Height: | Size: 86 KiB |
8
.github/screenshots/readme.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Screenshots
|
||||
|
||||
These screenshots are taken from our public [Demo](https://demo.homebox.software) instance.
|
||||
Note that whilst we will make every effort to ensure that these are maintained and updated, they may be outdated or missing functionality and we would always advise reviewing our demo instances:
|
||||
|
||||
- [Demo](https://demo.homebox.software)
|
||||
- [Nightly](https://nightly.homebox.software)
|
||||
- [VNext](https://vnext.homebox.software/)
|
||||
117
.github/scripts/update_currencies.py
vendored
@@ -1,16 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter, Retry
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
|
||||
API_URL = 'https://restcountries.com/v3.1/all?fields=name,common,currencies'
|
||||
# Default to a pinned commit for supply-chain security
|
||||
DEFAULT_ISO_4217_URL = 'https://raw.githubusercontent.com/datasets/currency-codes/052b3088938ba32028a14e75040c286c5e142145/data/codes-all.csv'
|
||||
ISO_4217_URL = os.environ.get('ISO_4217_URL', DEFAULT_ISO_4217_URL)
|
||||
SAVE_PATH = Path('backend/internal/core/currencies/currencies.json')
|
||||
TIMEOUT = 10 # seconds
|
||||
|
||||
# Known currency decimal overrides
|
||||
CURRENCY_DECIMAL_OVERRIDES = {
|
||||
"BTC": 8, # Bitcoin uses 8 decimal places
|
||||
"JPY": 0, # Japanese Yen has no decimal places
|
||||
"BHD": 3, # Bahraini Dinar uses 3 decimal places
|
||||
}
|
||||
DEFAULT_DECIMALS = 2
|
||||
MIN_DECIMALS = 0
|
||||
MAX_DECIMALS = 6
|
||||
|
||||
|
||||
def setup_logging():
|
||||
logging.basicConfig(
|
||||
@@ -19,7 +36,93 @@ def setup_logging():
|
||||
)
|
||||
|
||||
|
||||
def get_currency_decimals(code, iso_data):
|
||||
"""
|
||||
Get the decimal places for a currency code.
|
||||
Checks overrides first, then ISO data, then uses default.
|
||||
Clamps result to safe range [MIN_DECIMALS, MAX_DECIMALS].
|
||||
"""
|
||||
# Normalize the input code
|
||||
normalized_code = (code or "").strip().upper()
|
||||
|
||||
# First check overrides
|
||||
if normalized_code in CURRENCY_DECIMAL_OVERRIDES:
|
||||
decimals = CURRENCY_DECIMAL_OVERRIDES[normalized_code]
|
||||
# Then check ISO data
|
||||
elif normalized_code in iso_data:
|
||||
decimals = iso_data[normalized_code]
|
||||
# Finally use default
|
||||
else:
|
||||
decimals = DEFAULT_DECIMALS
|
||||
|
||||
# Ensure it's an integer and clamp to safe range
|
||||
try:
|
||||
decimals = int(decimals)
|
||||
except (ValueError, TypeError):
|
||||
decimals = DEFAULT_DECIMALS
|
||||
|
||||
return max(MIN_DECIMALS, min(MAX_DECIMALS, decimals))
|
||||
|
||||
|
||||
def fetch_iso_4217_data():
|
||||
"""
|
||||
Fetch ISO 4217 currency data to get minor units (decimal places).
|
||||
Returns a dict mapping currency code to minor units.
|
||||
"""
|
||||
# Log the resolved URL for transparency
|
||||
logging.info("Fetching ISO 4217 data from: %s", ISO_4217_URL)
|
||||
if not ISO_4217_URL.lower().startswith("https://"):
|
||||
logging.error("Refusing non-HTTPS ISO_4217_URL: %s", ISO_4217_URL)
|
||||
return {}
|
||||
|
||||
session = requests.Session()
|
||||
retries = Retry(
|
||||
total=3,
|
||||
backoff_factor=1,
|
||||
status_forcelist=[429, 500, 502, 503, 504],
|
||||
allowed_methods=frozenset(['GET'])
|
||||
)
|
||||
session.mount('https://', HTTPAdapter(max_retries=retries))
|
||||
|
||||
try:
|
||||
# Add Accept header for CSV content
|
||||
headers = {'Accept': 'text/csv'}
|
||||
resp = session.get(ISO_4217_URL, timeout=TIMEOUT, headers=headers)
|
||||
resp.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error("Failed to fetch ISO 4217 data: %s", e)
|
||||
return {}
|
||||
|
||||
# Parse CSV data
|
||||
iso_data = {}
|
||||
try:
|
||||
# Decode with utf-8-sig to strip BOM if present
|
||||
csv_content = resp.content.decode('utf-8-sig')
|
||||
csv_reader = csv.DictReader(io.StringIO(csv_content))
|
||||
|
||||
for row in csv_reader:
|
||||
code = row.get('AlphabeticCode', '').strip()
|
||||
minor_unit = row.get('MinorUnit', '').strip()
|
||||
|
||||
if code and minor_unit != 'N.A.':
|
||||
try:
|
||||
# Convert minor unit to int (decimal places)
|
||||
iso_data[code] = int(minor_unit) if minor_unit.isdigit() else 2
|
||||
except (ValueError, TypeError):
|
||||
iso_data[code] = 2 # Default to 2 if parsing fails
|
||||
|
||||
logging.info("Successfully loaded decimal data for %d currencies from ISO 4217", len(iso_data))
|
||||
return iso_data
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Failed to parse ISO 4217 CSV data: %s", e)
|
||||
return {}
|
||||
|
||||
|
||||
def fetch_currencies():
|
||||
# First, fetch ISO 4217 data for decimal places
|
||||
iso_data = fetch_iso_4217_data()
|
||||
|
||||
session = requests.Session()
|
||||
retries = Retry(
|
||||
total=3,
|
||||
@@ -46,11 +149,15 @@ def fetch_currencies():
|
||||
for country in countries:
|
||||
country_name = country.get('name', {}).get('common') or "Unknown"
|
||||
for code, info in country.get('currencies', {}).items():
|
||||
# Get decimal places using the helper function
|
||||
decimals = get_currency_decimals(code, iso_data)
|
||||
|
||||
results.append({
|
||||
'code': code,
|
||||
'local': country_name,
|
||||
'symbol': info.get('symbol', ''),
|
||||
'name': info.get('name', '')
|
||||
'code': code,
|
||||
'local': country_name,
|
||||
'symbol': info.get('symbol', ''),
|
||||
'name': info.get('name', ''),
|
||||
'decimals': decimals
|
||||
})
|
||||
|
||||
# sort by country name for consistency
|
||||
|
||||
349
.github/scripts/update_language_names.py
vendored
Executable file
@@ -0,0 +1,349 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to automatically update language names in the English translation file.
|
||||
Queries Weblate for translation completion and language names.
|
||||
Only adds languages with >=80% completion to en.json.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import requests
|
||||
from babel import Locale, UnknownLocaleError
|
||||
|
||||
LOCALES_DIR = Path('frontend/locales')
|
||||
EN_JSON_PATH = LOCALES_DIR / 'en.json'
|
||||
WEBLATE_API_URL = 'https://translate.sysadminsmedia.com/api'
|
||||
WEBLATE_PROJECT = 'homebox'
|
||||
WEBLATE_COMPONENT = 'frontend'
|
||||
COMPLETION_THRESHOLD = 80.0 # Minimum completion percentage to include language
|
||||
TIMEOUT = 10 # seconds
|
||||
|
||||
|
||||
def setup_logging():
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s: %(message)s'
|
||||
)
|
||||
|
||||
|
||||
def get_locale_files() -> List[str]:
|
||||
"""Get all locale codes from JSON files in the locales directory."""
|
||||
if not LOCALES_DIR.exists():
|
||||
logging.error("Locales directory not found: %s", LOCALES_DIR)
|
||||
return []
|
||||
|
||||
locale_codes = []
|
||||
for file in sorted(LOCALES_DIR.glob('*.json')):
|
||||
# Extract locale code from filename (e.g., "en.json" -> "en")
|
||||
locale_code = file.stem
|
||||
# Validate locale code format - should not contain dots
|
||||
if '.' not in locale_code:
|
||||
locale_codes.append(locale_code)
|
||||
else:
|
||||
logging.warning("Skipping invalid locale code: %s", locale_code)
|
||||
|
||||
logging.info("Found %d locale files", len(locale_codes))
|
||||
return sorted(locale_codes)
|
||||
|
||||
|
||||
def fetch_weblate_translations() -> Optional[Dict[str, Dict]]:
|
||||
"""
|
||||
Fetch translation statistics from Weblate API.
|
||||
|
||||
Returns:
|
||||
Dict mapping locale code to translation data (percent, name, native_name)
|
||||
or None if API is unavailable
|
||||
"""
|
||||
url = f"{WEBLATE_API_URL}/components/{WEBLATE_PROJECT}/{WEBLATE_COMPONENT}/translations/"
|
||||
|
||||
try:
|
||||
# Weblate API may require pagination
|
||||
translations = {}
|
||||
page_url = url
|
||||
|
||||
while page_url:
|
||||
logging.info("Fetching translations from Weblate: %s", page_url)
|
||||
resp = requests.get(page_url, timeout=TIMEOUT)
|
||||
|
||||
if resp.status_code != 200:
|
||||
logging.warning("Weblate API returned status %d", resp.status_code)
|
||||
return None
|
||||
|
||||
data = resp.json()
|
||||
|
||||
for trans in data.get('results', []):
|
||||
# Weblate uses underscores, we use hyphens
|
||||
locale_code = trans.get('language_code', '').replace('_', '-')
|
||||
percent = trans.get('translated_percent', 0.0)
|
||||
|
||||
lang_info = trans.get('language', {})
|
||||
english_name = lang_info.get('name', '')
|
||||
native_name = lang_info.get('native', '')
|
||||
|
||||
translations[locale_code] = {
|
||||
'percent': percent,
|
||||
'english_name': english_name,
|
||||
'native_name': native_name
|
||||
}
|
||||
|
||||
# Check for next page
|
||||
page_url = data.get('next')
|
||||
|
||||
logging.info("Fetched %d translations from Weblate", len(translations))
|
||||
return translations
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.warning("Failed to fetch from Weblate API: %s", e)
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error("Unexpected error fetching Weblate data: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
def get_language_name_from_babel(locale_code: str) -> Optional[str]:
|
||||
"""
|
||||
Get the language name using Babel in format "English (Native)".
|
||||
Special handling for variants that need disambiguation (Portuguese, Chinese).
|
||||
|
||||
Args:
|
||||
locale_code: Language/locale code (e.g., 'en', 'pt-BR', 'zh-CN')
|
||||
|
||||
Returns:
|
||||
Language name in format "English (Native)" or None if cannot parse
|
||||
"""
|
||||
try:
|
||||
# Special handling for ar-AA (non-standard code, use standard 'ar')
|
||||
if locale_code == 'ar-AA':
|
||||
locale = Locale.parse('ar')
|
||||
else:
|
||||
# Parse locale code using Babel
|
||||
locale = Locale.parse(locale_code.replace('-', '_'))
|
||||
|
||||
# Get English display name
|
||||
english_name = locale.get_display_name('en')
|
||||
|
||||
# Get native display name
|
||||
native_name = locale.get_display_name(locale)
|
||||
|
||||
if not english_name:
|
||||
return None
|
||||
|
||||
# Special handling for Portuguese variants (distinguish Brazil vs Portugal)
|
||||
if locale_code == 'pt-BR':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Portuguese — Brazil ({native_base})"
|
||||
elif locale_code == 'pt-PT':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Portuguese — Portugal ({native_base})"
|
||||
|
||||
# Special handling for Chinese variants (distinguish Simplified/Traditional and regions)
|
||||
if locale_code == 'zh-CN':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Simplified ({native_base})"
|
||||
elif locale_code == 'zh-TW':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Traditional ({native_base})"
|
||||
elif locale_code == 'zh-HK':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Hong Kong ({native_base})"
|
||||
elif locale_code == 'zh-MO':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Macau ({native_base})"
|
||||
|
||||
# Format: "English (Native)" if native name differs and is available
|
||||
if native_name and native_name != english_name:
|
||||
# Clean up nested parentheses for complex locales
|
||||
if '(' in english_name and '(' in native_name:
|
||||
# For cases like "Japanese (Japan) (日本語 (日本))"
|
||||
# Simplify to "Japanese (日本語)"
|
||||
english_base = english_name.split('(')[0].strip()
|
||||
native_base = native_name.split('(')[0].strip()
|
||||
return f"{english_base} ({native_base})"
|
||||
else:
|
||||
return f"{english_name} ({native_name})"
|
||||
else:
|
||||
return english_name
|
||||
|
||||
except (UnknownLocaleError, ValueError) as e:
|
||||
logging.debug("Could not parse locale '%s' with Babel: %s", locale_code, e)
|
||||
return None
|
||||
|
||||
|
||||
def get_language_name(locale_code: str, weblate_data: Optional[Dict] = None) -> Optional[str]:
|
||||
"""
|
||||
Get the display name for a locale code.
|
||||
Priority: Weblate API > Babel > None
|
||||
|
||||
Args:
|
||||
locale_code: Language/locale code (e.g., 'en', 'pt-BR', 'zh-CN')
|
||||
weblate_data: Translation data from Weblate (if available)
|
||||
|
||||
Returns:
|
||||
Language name in format "English (Native)" or None if invalid
|
||||
"""
|
||||
# Validate locale code format
|
||||
if '.' in locale_code or locale_code.startswith('languages.'):
|
||||
logging.error("Invalid locale code format: %s", locale_code)
|
||||
return None
|
||||
|
||||
# Try Weblate first
|
||||
if weblate_data and locale_code in weblate_data:
|
||||
english_name = weblate_data[locale_code].get('english_name', '')
|
||||
native_name = weblate_data[locale_code].get('native_name', '')
|
||||
|
||||
if english_name:
|
||||
# Format: "English (Native)" if both names available and different
|
||||
if native_name and native_name != english_name:
|
||||
return f"{english_name} ({native_name})"
|
||||
else:
|
||||
return english_name
|
||||
|
||||
# Fallback to Babel
|
||||
babel_name = get_language_name_from_babel(locale_code)
|
||||
if babel_name:
|
||||
return babel_name
|
||||
|
||||
# If all else fails, return None (don't guess)
|
||||
logging.warning("Could not determine language name for: %s", locale_code)
|
||||
return None
|
||||
|
||||
|
||||
def load_en_json() -> dict:
|
||||
"""Load the English translation JSON file."""
|
||||
if not EN_JSON_PATH.exists():
|
||||
logging.error("English translation file not found: %s", EN_JSON_PATH)
|
||||
return {}
|
||||
|
||||
try:
|
||||
with EN_JSON_PATH.open('r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (IOError, json.JSONDecodeError) as e:
|
||||
logging.error("Failed to load %s: %s", EN_JSON_PATH, e)
|
||||
return {}
|
||||
|
||||
|
||||
def save_en_json(data: dict):
|
||||
"""Save the English translation JSON file."""
|
||||
try:
|
||||
with EN_JSON_PATH.open('w', encoding='utf-8') as f:
|
||||
# Use 4-space indentation to match existing file format
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
# Add newline at end of file
|
||||
f.write('\n')
|
||||
logging.info("Saved updated en.json")
|
||||
except IOError as e:
|
||||
logging.error("Failed to save %s: %s", EN_JSON_PATH, e)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def update_language_names(en_data: dict, locale_codes: List[str], weblate_data: Optional[Dict] = None) -> bool:
|
||||
"""
|
||||
Update the languages section in en.json.
|
||||
- Add new languages with >=80% completion (from Weblate) or that exist as locale files
|
||||
- Never remove existing entries (even if completion drops below 80%)
|
||||
|
||||
Args:
|
||||
en_data: The parsed en.json data
|
||||
locale_codes: List of all locale codes from files
|
||||
weblate_data: Translation data from Weblate (if available)
|
||||
|
||||
Returns:
|
||||
True if changes were made, False otherwise
|
||||
"""
|
||||
# Ensure languages section exists
|
||||
if 'languages' not in en_data:
|
||||
en_data['languages'] = {}
|
||||
logging.info("Created 'languages' section in en.json")
|
||||
|
||||
languages = en_data['languages']
|
||||
original_languages = languages.copy()
|
||||
|
||||
# Process each locale file
|
||||
added_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for locale_code in locale_codes:
|
||||
# Skip if already in languages (never remove existing entries)
|
||||
if locale_code in languages:
|
||||
continue
|
||||
|
||||
# Check Weblate completion threshold if data available
|
||||
if weblate_data and locale_code in weblate_data:
|
||||
percent = weblate_data[locale_code].get('percent', 0.0)
|
||||
|
||||
if percent < COMPLETION_THRESHOLD:
|
||||
logging.info("Skipping %s: %.1f%% completion (threshold: %.1f%%)",
|
||||
locale_code, percent, COMPLETION_THRESHOLD)
|
||||
skipped_count += 1
|
||||
continue
|
||||
else:
|
||||
logging.info("Including %s: %.1f%% completion", locale_code, percent)
|
||||
else:
|
||||
# If Weblate data not available, include locale file but log warning
|
||||
logging.info("Including %s: Weblate data not available, locale file exists", locale_code)
|
||||
|
||||
# Get language name
|
||||
language_name = get_language_name(locale_code, weblate_data)
|
||||
|
||||
if language_name:
|
||||
languages[locale_code] = language_name
|
||||
logging.info("Added language: %s = %s", locale_code, language_name)
|
||||
added_count += 1
|
||||
else:
|
||||
logging.warning("Skipping %s: could not determine language name", locale_code)
|
||||
skipped_count += 1
|
||||
|
||||
# Sort languages alphabetically by key
|
||||
en_data['languages'] = dict(sorted(languages.items()))
|
||||
|
||||
# Check if anything changed
|
||||
changed = (original_languages != en_data['languages'])
|
||||
|
||||
if changed:
|
||||
logging.info("Updated %d language names, skipped %d", added_count, skipped_count)
|
||||
else:
|
||||
logging.info("All languages already present, no changes needed")
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def main():
|
||||
setup_logging()
|
||||
logging.info("🔄 Starting language names update")
|
||||
|
||||
# Get all locale files
|
||||
locale_codes = get_locale_files()
|
||||
if not locale_codes:
|
||||
logging.error("No locale files found")
|
||||
sys.exit(1)
|
||||
|
||||
# Load English translation file
|
||||
en_data = load_en_json()
|
||||
if not en_data:
|
||||
logging.error("Failed to load English translation file")
|
||||
sys.exit(1)
|
||||
|
||||
# Fetch Weblate translation statistics
|
||||
weblate_data = fetch_weblate_translations()
|
||||
if weblate_data:
|
||||
logging.info("Successfully fetched Weblate data for %d languages", len(weblate_data))
|
||||
else:
|
||||
logging.warning("Weblate data not available, proceeding with locale files only")
|
||||
|
||||
# Update language names
|
||||
changed = update_language_names(en_data, locale_codes, weblate_data)
|
||||
|
||||
if changed:
|
||||
save_en_json(en_data)
|
||||
logging.info("✅ Language names updated successfully")
|
||||
else:
|
||||
logging.info("✅ No updates needed, en.json is already up-to-date")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
259
.github/scripts/upgrade-test/README.md
vendored
Normal 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, labels, 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
|
||||
|
||||
#### Labels
|
||||
- **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 labels 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"]
|
||||
},
|
||||
"labels": {...},
|
||||
"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
|
||||
413
.github/scripts/upgrade-test/create-test-data.sh
vendored
Executable 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 labels
|
||||
|
||||
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 label
|
||||
create_label() {
|
||||
local token=$1
|
||||
local name=$2
|
||||
local description=$3
|
||||
|
||||
echo "Creating label: $name" >&2
|
||||
|
||||
local response=$(curl -s -X POST \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$name\",\"description\":\"$description\"}" \
|
||||
"$API_URL/labels")
|
||||
|
||||
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 labels for each group ==="
|
||||
|
||||
# Create labels for group 1
|
||||
label1=$(create_label "$user1_token" "Electronics" "Electronic devices")
|
||||
label1_id=$(echo "$label1" | jq -r '.id // empty')
|
||||
echo "Created label: Electronics (ID: $label1_id)"
|
||||
|
||||
label2=$(create_label "$user1_token" "Important" "High priority items")
|
||||
label2_id=$(echo "$label2" | jq -r '.id // empty')
|
||||
echo "Created label: Important (ID: $label2_id)"
|
||||
|
||||
# Create label for group 2
|
||||
label3=$(create_label "$user6_token" "Work Equipment" "Items for work")
|
||||
label3_id=$(echo "$label3" | jq -r '.id // empty')
|
||||
echo "Created label: Work Equipment (ID: $label3_id)"
|
||||
|
||||
# Store labels
|
||||
jq --arg lab1 "$label1_id" \
|
||||
--arg lab2 "$label2_id" \
|
||||
--arg lab3 "$label3_id" \
|
||||
'.labels = {"group1":[$lab1,$lab2],"group2":[$lab3]}' \
|
||||
"$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 " - Labels 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
|
||||
96
.github/workflows/binaries-publish.yaml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Publish Release Binaries
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags: [ 'v*.*.*' ]
|
||||
|
||||
@@ -8,21 +9,25 @@ jobs:
|
||||
goreleaser:
|
||||
name: goreleaser
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
hashes: ${{ steps.binary.outputs.hashes }}
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
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
|
||||
@@ -36,8 +41,15 @@ jobs:
|
||||
run: |
|
||||
go install github.com/sigstore/cosign/cmd/cosign@latest
|
||||
|
||||
- name: Install Syft
|
||||
working-directory: backend
|
||||
run: |
|
||||
go install github.com/anchore/syft/cmd/syft@latest
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
id: releaser
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
|
||||
with:
|
||||
workdir: "backend"
|
||||
distribution: goreleaser
|
||||
@@ -45,3 +57,75 @@ jobs:
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COSIGN_PWD: ${{ secrets.COSIGN_PWD }}
|
||||
COSIGN_YES: "true"
|
||||
|
||||
- name: Generate binary hashes
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
id: binary
|
||||
env:
|
||||
ARTIFACTS: "${{ steps.releaser.outputs.artifacts }}"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path')
|
||||
echo "hashes=$(cat $checksum_file | base64 -w0)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run GoReleaser No Release
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
|
||||
with:
|
||||
workdir: "backend"
|
||||
distribution: goreleaser
|
||||
version: "~> v2"
|
||||
args: release --clean --snapshot --skip=publish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COSIGN_PWD: ${{ secrets.COSIGN_PWD }}
|
||||
COSIGN_YES: "true"
|
||||
|
||||
binary-provenance:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs: [ goreleaser ]
|
||||
permissions:
|
||||
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@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a
|
||||
with:
|
||||
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
|
||||
upload-assets: true # upload to a new release
|
||||
|
||||
verification-with-slsa-verifier:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs: [goreleaser, binary-provenance]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Install the verifier
|
||||
uses: slsa-framework/slsa-verifier/actions/installer@ea584f4502babc6f60d9bc799dbbb13c1caa9ee6
|
||||
|
||||
- name: Download assets
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PROVENANCE: "${{ needs.binary-provenance.outputs.provenance-name }}"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh -R "$GITHUB_REPOSITORY" release download "$GITHUB_REF_NAME" -p "*.tar.gz"
|
||||
gh -R "$GITHUB_REPOSITORY" release download "$GITHUB_REF_NAME" -p "*.zip"
|
||||
gh -R "$GITHUB_REPOSITORY" release download "$GITHUB_REF_NAME" -p "$PROVENANCE"
|
||||
- name: Verify assets
|
||||
env:
|
||||
CHECKSUMS: ${{ needs.goreleaser.outputs.hashes }}
|
||||
PROVENANCE: "${{ needs.binary-provenance.outputs.provenance-name }}"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
checksums=$(echo "$CHECKSUMS" | base64 -d)
|
||||
while read -r line; do
|
||||
fn=$(echo $line | cut -d ' ' -f2)
|
||||
echo "Verifying $fn"
|
||||
slsa-verifier verify-artifact --provenance-path "$PROVENANCE" \
|
||||
--source-uri "github.com/$GITHUB_REPOSITORY" \
|
||||
--source-tag "$GITHUB_REF_NAME" \
|
||||
"$fn"
|
||||
done <<<"$checksums"
|
||||
|
||||
@@ -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
|
||||
|
||||
14
.github/workflows/copilot-setup-steps.yml
vendored
@@ -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 }}
|
||||
|
||||
|
||||
67
.github/workflows/docker-publish-hardened.yaml
vendored
@@ -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: |
|
||||
@@ -95,13 +96,13 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392
|
||||
with:
|
||||
image: ghcr.io/amitie10g/binfmt:latest
|
||||
image: ghcr.io/sysadminsmedia/binfmt:latest
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
|
||||
with:
|
||||
driver-opts: |
|
||||
image=ghcr.io/amitie10g/buildkit:master
|
||||
image=ghcr.io/sysadminsmedia/buildkit:master
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
@@ -118,10 +119,18 @@ jobs:
|
||||
VERSION=${{ github.ref_name }}
|
||||
COMMIT=${{ github.sha }}
|
||||
BUILD_TIME=${{ env.BUILD_TIME }}
|
||||
provenance: true
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
|
||||
|
||||
- name: Attest platform-specific images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REPO }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
@@ -173,7 +182,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
|
||||
with:
|
||||
driver-opts: |
|
||||
image=ghcr.io/amitie10g/buildkit:master
|
||||
image=ghcr.io/sysadminsmedia/buildkit:master
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
@@ -196,13 +205,45 @@ jobs:
|
||||
id: push-ghcr
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
|
||||
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-ghcr.out
|
||||
digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-ghcr.out | head -n1 || true)
|
||||
if [ -z "$digest" ]; then
|
||||
echo "No digest found in imagetools output:"
|
||||
cat /tmp/push-ghcr.out
|
||||
exit 1
|
||||
fi
|
||||
echo "digest=$digest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Attest GHCR images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REPO }}
|
||||
subject-digest: ${{ steps.push-ghcr.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Create manifest list and push Dockerhub
|
||||
id: push-dockerhub
|
||||
working-directory: /tmp/digests
|
||||
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *)
|
||||
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-dockerhub.out
|
||||
digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-dockerhub.out | head -n1 || true)
|
||||
if [ -z "$digest" ]; then
|
||||
echo "No digest found in imagetools output:"
|
||||
cat /tmp/push-dockerhub.out
|
||||
exit 1
|
||||
fi
|
||||
echo "digest=$digest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Attest Dockerhub images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
|
||||
with:
|
||||
subject-name: docker.io/${{ env.DOCKERHUB_REPO }}
|
||||
subject-digest: ${{ steps.push-dockerhub.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
91
.github/workflows/docker-publish-rootless.yaml
vendored
@@ -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,40 @@ 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 }}
|
||||
|
||||
- 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/amitie10g/binfmt:latest
|
||||
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/amitie10g/buildkit:master
|
||||
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
|
||||
@@ -120,10 +121,18 @@ jobs:
|
||||
build-args: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
COMMIT=${{ github.sha }}
|
||||
provenance: true
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
|
||||
|
||||
- name: Attest platform-specific images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REPO }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
@@ -131,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/*
|
||||
@@ -151,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/amitie10g/buildkit:master
|
||||
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/') }}
|
||||
@@ -198,13 +207,45 @@ jobs:
|
||||
id: push-ghcr
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
|
||||
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-ghcr.out
|
||||
digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-ghcr.out | head -n1 || true)
|
||||
if [ -z "$digest" ]; then
|
||||
echo "No digest found in imagetools output:"
|
||||
cat /tmp/push-ghcr.out
|
||||
exit 1
|
||||
fi
|
||||
echo "digest=$digest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Attest GHCR images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REPO }}
|
||||
subject-digest: ${{ steps.push-ghcr.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Create manifest list and push Dockerhub
|
||||
id: push-dockerhub
|
||||
working-directory: /tmp/digests
|
||||
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *)
|
||||
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-dockerhub.out
|
||||
digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-dockerhub.out | head -n1 || true)
|
||||
if [ -z "$digest" ]; then
|
||||
echo "No digest found in imagetools output:"
|
||||
cat /tmp/push-dockerhub.out
|
||||
exit 1
|
||||
fi
|
||||
echo "digest=$digest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Attest Dockerhub images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
|
||||
with:
|
||||
subject-name: docker.io/${{ env.DOCKERHUB_REPO }}
|
||||
subject-digest: ${{ steps.push-dockerhub.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
89
.github/workflows/docker-publish.yaml
vendored
@@ -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,40 @@ 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 }}
|
||||
|
||||
- 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/amitie10g/binfmt:latest
|
||||
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/amitie10g/buildkit:master
|
||||
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 }}
|
||||
@@ -113,10 +114,18 @@ jobs:
|
||||
build-args: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
COMMIT=${{ github.sha }}
|
||||
provenance: true
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
|
||||
- name: Attest platform-specific images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REPO }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
@@ -124,7 +133,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/*
|
||||
@@ -144,35 +153,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/amitie10g/buildkit:master
|
||||
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/') }}
|
||||
@@ -189,13 +198,45 @@ jobs:
|
||||
id: push-ghcr
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
|
||||
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-ghcr.out
|
||||
digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-ghcr.out | head -n1 || true)
|
||||
if [ -z "$digest" ]; then
|
||||
echo "No digest found in imagetools output:"
|
||||
cat /tmp/push-ghcr.out
|
||||
exit 1
|
||||
fi
|
||||
echo "digest=$digest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Attest GHCR images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REPO }}
|
||||
subject-digest: ${{ steps.push-ghcr.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Create manifest list and push Dockerhub
|
||||
id: push-dockerhub
|
||||
working-directory: /tmp/digests
|
||||
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *)
|
||||
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-dockerhub.out
|
||||
digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-dockerhub.out | head -n1 || true)
|
||||
if [ -z "$digest" ]; then
|
||||
echo "No digest found in imagetools output:"
|
||||
cat /tmp/push-dockerhub.out
|
||||
exit 1
|
||||
fi
|
||||
echo "digest=$digest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Attest Dockerhub images
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
|
||||
if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/'))
|
||||
with:
|
||||
subject-name: docker.io/${{ env.DOCKERHUB_REPO }}
|
||||
subject-digest: ${{ steps.push-dockerhub.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
34
.github/workflows/e2e-partial.yaml
vendored
@@ -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
@@ -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'
|
||||
});
|
||||
}
|
||||
14
.github/workflows/partial-backend.yaml
vendored
@@ -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
|
||||
|
||||
42
.github/workflows/partial-frontend.yaml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/pull-requests.yaml
vendored
@@ -1,5 +1,11 @@
|
||||
name: Pull Request CI
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
|
||||
21
.github/workflows/update-currencies.yml
vendored
@@ -5,18 +5,22 @@ on:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-currencies:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
|
||||
with:
|
||||
python-version: '3.8'
|
||||
cache: 'pip'
|
||||
@@ -25,15 +29,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
pip install -r .github/workflows/update-currencies/requirements.txt
|
||||
|
||||
- name: Run currency update script
|
||||
run: python .github/scripts/update_currencies.py
|
||||
|
||||
- name: Check for file changes
|
||||
id: changes
|
||||
- name: Check for currencies.json changes
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
if git diff --quiet -- backend/internal/core/currencies/currencies.json; then
|
||||
echo "changed=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "changed=true" >> $GITHUB_ENV
|
||||
@@ -41,14 +44,16 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
if: env.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@v7.0.8
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: update-currencies
|
||||
base: main
|
||||
title: "Update currencies.json"
|
||||
commit-message: "chore: update currencies.json"
|
||||
path: backend/internal/core/currencies/currencies.json
|
||||
path: .
|
||||
add-paths: |
|
||||
backend/internal/core/currencies/currencies.json
|
||||
|
||||
- name: No updates needed
|
||||
if: env.changed == 'false'
|
||||
|
||||
70
.github/workflows/update-language-names.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Update Language Names
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'frontend/locales/*.json'
|
||||
- '.github/scripts/update_language_names.py'
|
||||
- '.github/workflows/update-language-names.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-language-names:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
|
||||
with:
|
||||
python-version: '3.8'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: .github/workflows/update-languages/requirements.txt
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r .github/workflows/update-languages/requirements.txt
|
||||
|
||||
- name: Run language names update script
|
||||
run: python .github/scripts/update_language_names.py
|
||||
|
||||
- name: Check for en.json changes
|
||||
run: |
|
||||
if git diff --quiet -- frontend/locales/en.json; then
|
||||
echo "changed=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "changed=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
if: env.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: automation/update-language-file
|
||||
base: main
|
||||
title: "Update language names in en.json"
|
||||
commit-message: "chore: update language names in en.json"
|
||||
body: |
|
||||
This PR automatically updates the language names in `frontend/locales/en.json` based on the available locale files.
|
||||
|
||||
New languages have been added to ensure all locale files have corresponding language names in the English translation file.
|
||||
|
||||
🤖 This PR was automatically created by the update-language-names workflow.
|
||||
path: .
|
||||
add-paths: |
|
||||
frontend/locales/en.json
|
||||
|
||||
- name: No updates needed
|
||||
if: env.changed == 'false'
|
||||
run: echo "✅ en.json language names are already up-to-date"
|
||||
2
.github/workflows/update-languages/requirements.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
babel
|
||||
requests
|
||||
177
.github/workflows/upgrade-test.yaml
vendored
Normal 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
|
||||
4
.gitignore
vendored
@@ -60,10 +60,12 @@ backend/app/api/static/public/*
|
||||
backend/api
|
||||
|
||||
docs/.vitepress/cache/
|
||||
/.data/
|
||||
.data/
|
||||
|
||||
# Playwright
|
||||
frontend/test-results/
|
||||
frontend/playwright-report/
|
||||
frontend/blob-report/
|
||||
frontend/playwright/.cache/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
603
.gitlab-ci.yml
Normal file
@@ -0,0 +1,603 @@
|
||||
include:
|
||||
- template: Jobs/SAST.gitlab-ci.yml
|
||||
- component: $CI_SERVER_FQDN/components/code-quality-oss/codequality-os-scanners-integration/codequality-oss@1.1.4
|
||||
- component: $CI_SERVER_FQDN/components/code-intelligence/golang-code-intel@v0.3.1
|
||||
- component: $CI_SERVER_FQDN/components/code-intelligence/typescript-code-intel@v0.3.1
|
||||
inputs:
|
||||
node_version: 24
|
||||
- component: $CI_SERVER_FQDN/components/secret-detection/secret-detection@2.1.0
|
||||
|
||||
variables:
|
||||
GITLAB_ADVANCED_SAST_ENABLED: 'true'
|
||||
ADVANCED_SAST_PARTIAL_SCAN: 'differential'
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
# Registry configuration - adjust as needed
|
||||
CI_REGISTRY_IMAGE: $CI_REGISTRY/$CI_PROJECT_PATH
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build-binaries
|
||||
- build-docker
|
||||
- release
|
||||
|
||||
# ==========================================
|
||||
# Test Jobs
|
||||
# ==========================================
|
||||
|
||||
# Backend Tests (Go)
|
||||
test:backend:
|
||||
stage: test
|
||||
image: golang:1.24
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- backend/go.sum
|
||||
paths:
|
||||
- backend/.go-pkg-cache/
|
||||
policy: pull-push
|
||||
before_script:
|
||||
- export GOMODCACHE=$(pwd)/backend/.go-pkg-cache
|
||||
# Install Task
|
||||
- sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
|
||||
script:
|
||||
- cd backend
|
||||
- task go:lint
|
||||
- task go:build
|
||||
- task go:coverage
|
||||
coverage: '/coverage: \d+.\d+% of statements/'
|
||||
artifacts:
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: backend/coverage.out
|
||||
paths:
|
||||
- backend/coverage.out
|
||||
expire_in: 7 days
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# Frontend Lint and Typecheck
|
||||
test:frontend:lint:
|
||||
stage: test
|
||||
image: node:22-alpine
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- frontend/pnpm-lock.yaml
|
||||
paths:
|
||||
- frontend/node_modules/
|
||||
- .pnpm-store/
|
||||
policy: pull-push
|
||||
before_script:
|
||||
- npm install -g pnpm@9.15.3
|
||||
- pnpm config set store-dir $(pwd)/.pnpm-store
|
||||
script:
|
||||
- cd frontend
|
||||
- pnpm install --frozen-lockfile
|
||||
- pnpm run lint:ci
|
||||
- pnpm run typecheck
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# Frontend Integration Tests (SQLite)
|
||||
test:frontend:integration:
|
||||
stage: test
|
||||
image: node:22
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- frontend/pnpm-lock.yaml
|
||||
paths:
|
||||
- frontend/node_modules/
|
||||
- .pnpm-store/
|
||||
policy: pull-push
|
||||
- key:
|
||||
files:
|
||||
- backend/go.sum
|
||||
paths:
|
||||
- backend/.go-pkg-cache/
|
||||
policy: pull-push
|
||||
before_script:
|
||||
- npm install -g pnpm@9.15.3
|
||||
- pnpm config set store-dir $(pwd)/.pnpm-store
|
||||
# Install Task
|
||||
- sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
|
||||
# Install Go
|
||||
- wget -q https://go.dev/dl/go1.24.0.linux-amd64.tar.gz
|
||||
- tar -C /usr/local -xzf go1.24.0.linux-amd64.tar.gz
|
||||
- export PATH=$PATH:/usr/local/go/bin
|
||||
- export GOMODCACHE=$(pwd)/backend/.go-pkg-cache
|
||||
script:
|
||||
- cd frontend
|
||||
- pnpm install --frozen-lockfile
|
||||
- cd ..
|
||||
- task test:ci
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# Frontend Integration Tests (PostgreSQL Matrix)
|
||||
test:frontend:integration:postgresql:
|
||||
stage: test
|
||||
image: node:22
|
||||
services:
|
||||
- name: postgres:${POSTGRES_VERSION}
|
||||
alias: postgres
|
||||
variables:
|
||||
POSTGRES_USER: homebox
|
||||
POSTGRES_PASSWORD: homebox
|
||||
POSTGRES_DB: homebox
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
parallel:
|
||||
matrix:
|
||||
- POSTGRES_VERSION: ["17", "16", "15"]
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- frontend/pnpm-lock.yaml
|
||||
paths:
|
||||
- frontend/node_modules/
|
||||
- .pnpm-store/
|
||||
policy: pull-push
|
||||
- key:
|
||||
files:
|
||||
- backend/go.sum
|
||||
paths:
|
||||
- backend/.go-pkg-cache/
|
||||
policy: pull-push
|
||||
before_script:
|
||||
- npm install -g pnpm@9.15.3
|
||||
- pnpm config set store-dir $(pwd)/.pnpm-store
|
||||
# Install Task
|
||||
- sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
|
||||
# Install Go
|
||||
- wget -q https://go.dev/dl/go1.24.0.linux-amd64.tar.gz
|
||||
- tar -C /usr/local -xzf go1.24.0.linux-amd64.tar.gz
|
||||
- export PATH=$PATH:/usr/local/go/bin
|
||||
- export GOMODCACHE=$(pwd)/backend/.go-pkg-cache
|
||||
script:
|
||||
- cd frontend
|
||||
- pnpm install --frozen-lockfile
|
||||
- cd ..
|
||||
- task test:ci:postgresql
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# E2E Tests (Playwright) - Sharded
|
||||
test:e2e:playwright:
|
||||
stage: test
|
||||
image: mcr.microsoft.com/playwright:v1.48.2-jammy
|
||||
timeout: 1h
|
||||
parallel:
|
||||
matrix:
|
||||
- SHARD_INDEX: ["1", "2", "3", "4"]
|
||||
SHARD_TOTAL: "4"
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- frontend/pnpm-lock.yaml
|
||||
paths:
|
||||
- frontend/node_modules/
|
||||
- .pnpm-store/
|
||||
policy: pull-push
|
||||
- key:
|
||||
files:
|
||||
- backend/go.sum
|
||||
paths:
|
||||
- backend/.go-pkg-cache/
|
||||
policy: pull-push
|
||||
before_script:
|
||||
- npm install -g pnpm@9.15.3
|
||||
- pnpm config set store-dir $(pwd)/.pnpm-store
|
||||
# Install Task
|
||||
- sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
|
||||
# Install Go
|
||||
- wget -q https://go.dev/dl/go1.24.0.linux-amd64.tar.gz
|
||||
- tar -C /usr/local -xzf go1.24.0.linux-amd64.tar.gz
|
||||
- export PATH=$PATH:/usr/local/go/bin
|
||||
- export GOMODCACHE=$(pwd)/backend/.go-pkg-cache
|
||||
script:
|
||||
- cd frontend
|
||||
- pnpm install --frozen-lockfile
|
||||
- cd ..
|
||||
- cd backend && go mod download
|
||||
- task test:e2e -- --shard=$SHARD_INDEX/$SHARD_TOTAL
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- frontend/blob-report/
|
||||
expire_in: 2 days
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# E2E Reports Merge
|
||||
test:e2e:merge-reports:
|
||||
stage: test
|
||||
image: mcr.microsoft.com/playwright:v1.48.2-jammy
|
||||
needs:
|
||||
- test:e2e:playwright
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- frontend/pnpm-lock.yaml
|
||||
paths:
|
||||
- frontend/node_modules/
|
||||
- .pnpm-store/
|
||||
policy: pull
|
||||
before_script:
|
||||
- npm install -g pnpm@9.15.3
|
||||
- pnpm config set store-dir $(pwd)/.pnpm-store
|
||||
script:
|
||||
- cd frontend
|
||||
- pnpm install --frozen-lockfile
|
||||
# Download all blob reports
|
||||
- mkdir -p all-blob-reports
|
||||
# GitLab automatically downloads artifacts from dependencies
|
||||
- cp -r ../frontend/blob-report/* all-blob-reports/ || true
|
||||
- pnpm exec playwright merge-reports --reporter html,github ./all-blob-reports || true
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- frontend/playwright-report/
|
||||
expire_in: 30 days
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: always
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
when: always
|
||||
|
||||
# Update Currencies (Scheduled Job)
|
||||
update:currencies:
|
||||
stage: test
|
||||
image: python:3.11
|
||||
cache:
|
||||
key: python-currencies
|
||||
paths:
|
||||
- .pip-cache/
|
||||
before_script:
|
||||
- pip install --cache-dir .pip-cache -r .github/workflows/update-currencies/requirements.txt
|
||||
script:
|
||||
- python .github/scripts/update_currencies.py
|
||||
- |
|
||||
if git diff --quiet -- backend/internal/core/currencies/currencies.json; then
|
||||
echo "✅ currencies.json is already up-to-date"
|
||||
exit 0
|
||||
else
|
||||
echo "Changes detected in currencies.json"
|
||||
git config user.name "GitLab CI"
|
||||
git config user.email "ci@gitlab.com"
|
||||
git checkout -b update-currencies-$CI_COMMIT_SHORT_SHA
|
||||
git add backend/internal/core/currencies/currencies.json
|
||||
git commit -m "chore: update currencies.json"
|
||||
git push -o merge_request.create -o merge_request.target=$CI_DEFAULT_BRANCH -o merge_request.title="Update currencies.json" origin update-currencies-$CI_COMMIT_SHORT_SHA
|
||||
fi
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule" && $UPDATE_CURRENCIES == "true"
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $UPDATE_CURRENCIES == "true"
|
||||
|
||||
# ==========================================
|
||||
# Binary Build with GoReleaser
|
||||
# ==========================================
|
||||
build:binaries:
|
||||
stage: build-binaries
|
||||
image: golang:1.24
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- frontend/pnpm-lock.yaml
|
||||
paths:
|
||||
- frontend/node_modules/
|
||||
- .pnpm-store/
|
||||
policy: pull-push
|
||||
- key:
|
||||
files:
|
||||
- backend/go.sum
|
||||
paths:
|
||||
- backend/.go-pkg-cache/
|
||||
policy: pull-push
|
||||
before_script:
|
||||
# Install Node.js and pnpm for frontend build
|
||||
- curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
- apt-get install -y nodejs
|
||||
- npm install -g pnpm@9.15.3
|
||||
# Configure pnpm store
|
||||
- pnpm config set store-dir $(pwd)/.pnpm-store
|
||||
# Install GoReleaser
|
||||
- curl -sfL https://goreleaser.com/static/run | bash -s -- check
|
||||
- curl -sfL https://goreleaser.com/static/run | bash -s -- --version
|
||||
# Configure Go cache
|
||||
- export GOMODCACHE=$(pwd)/backend/.go-pkg-cache
|
||||
script:
|
||||
# Build frontend
|
||||
- cd frontend
|
||||
- pnpm install --frozen-lockfile
|
||||
- pnpm run build
|
||||
- cp -r ./.output/public ../backend/app/api/static/
|
||||
- cd ..
|
||||
# Run GoReleaser
|
||||
- cd backend
|
||||
- |
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
echo "Building release for tag: $CI_COMMIT_TAG"
|
||||
curl -sfL https://goreleaser.com/static/run | bash -s -- release --clean --skip=publish
|
||||
else
|
||||
echo "Building snapshot"
|
||||
curl -sfL https://goreleaser.com/static/run | bash -s -- release --clean --snapshot --skip=publish
|
||||
fi
|
||||
artifacts:
|
||||
name: "homebox-binaries-$CI_COMMIT_REF_SLUG"
|
||||
paths:
|
||||
- backend/dist/
|
||||
expire_in: 30 days
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
|
||||
# ==========================================
|
||||
# Docker Build Jobs - Regular
|
||||
# ==========================================
|
||||
.docker_build_template:
|
||||
stage: build-docker
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
variables:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKERFILE: Dockerfile
|
||||
IMAGE_SUFFIX: ""
|
||||
script:
|
||||
- export VERSION=${CI_COMMIT_TAG:-$CI_COMMIT_REF_NAME}
|
||||
- export COMMIT=$CI_COMMIT_SHA
|
||||
- export BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
- export CACHE_TAG=${IMAGE_SUFFIX:-regular}
|
||||
# Build and push for the specific platform with layer caching
|
||||
- |
|
||||
docker buildx create --use --name builder-${CI_JOB_ID} || true
|
||||
docker buildx build \
|
||||
--platform $PLATFORM \
|
||||
--build-arg VERSION=$VERSION \
|
||||
--build-arg COMMIT=$COMMIT \
|
||||
--build-arg BUILD_TIME=$BUILD_TIME \
|
||||
--cache-from type=registry,ref=$CI_REGISTRY_IMAGE/cache:${PLATFORM_PAIR}-${CACHE_TAG}-$CI_COMMIT_REF_SLUG \
|
||||
--cache-from type=registry,ref=$CI_REGISTRY_IMAGE/cache:${PLATFORM_PAIR}-${CACHE_TAG}-$CI_DEFAULT_BRANCH \
|
||||
--cache-to type=registry,ref=$CI_REGISTRY_IMAGE/cache:${PLATFORM_PAIR}-${CACHE_TAG}-$CI_COMMIT_REF_SLUG,mode=max \
|
||||
--file ./$DOCKERFILE \
|
||||
--tag $CI_REGISTRY_IMAGE${IMAGE_SUFFIX}:${CI_COMMIT_REF_SLUG}-${PLATFORM_PAIR} \
|
||||
--push \
|
||||
.
|
||||
docker buildx rm builder-${CI_JOB_ID} || true
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
|
||||
docker:build:amd64:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/amd64
|
||||
PLATFORM_PAIR: linux-amd64
|
||||
|
||||
docker:build:arm64:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/arm64
|
||||
PLATFORM_PAIR: linux-arm64
|
||||
|
||||
docker:build:armv7:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/arm/v7
|
||||
PLATFORM_PAIR: linux-arm-v7
|
||||
|
||||
# ==========================================
|
||||
# Docker Build Jobs - Rootless
|
||||
# ==========================================
|
||||
docker:build:rootless:amd64:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/amd64
|
||||
PLATFORM_PAIR: linux-amd64
|
||||
DOCKERFILE: Dockerfile.rootless
|
||||
IMAGE_SUFFIX: -rootless
|
||||
|
||||
docker:build:rootless:arm64:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/arm64
|
||||
PLATFORM_PAIR: linux-arm64
|
||||
DOCKERFILE: Dockerfile.rootless
|
||||
IMAGE_SUFFIX: -rootless
|
||||
|
||||
docker:build:rootless:armv7:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/arm/v7
|
||||
PLATFORM_PAIR: linux-arm-v7
|
||||
DOCKERFILE: Dockerfile.rootless
|
||||
IMAGE_SUFFIX: -rootless
|
||||
|
||||
# ==========================================
|
||||
# Docker Build Jobs - Hardened
|
||||
# ==========================================
|
||||
docker:build:hardened:amd64:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/amd64
|
||||
PLATFORM_PAIR: linux-amd64
|
||||
DOCKERFILE: Dockerfile.hardened
|
||||
IMAGE_SUFFIX: -hardened
|
||||
|
||||
docker:build:hardened:arm64:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/arm64
|
||||
PLATFORM_PAIR: linux-arm64
|
||||
DOCKERFILE: Dockerfile.hardened
|
||||
IMAGE_SUFFIX: -hardened
|
||||
|
||||
docker:build:hardened:armv7:
|
||||
extends: .docker_build_template
|
||||
variables:
|
||||
PLATFORM: linux/arm/v7
|
||||
PLATFORM_PAIR: linux-arm-v7
|
||||
DOCKERFILE: Dockerfile.hardened
|
||||
IMAGE_SUFFIX: -hardened
|
||||
|
||||
# ==========================================
|
||||
# Docker Manifest Creation - Regular
|
||||
# ==========================================
|
||||
docker:manifest:
|
||||
stage: release
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
script:
|
||||
- export VERSION=${CI_COMMIT_TAG:-$CI_COMMIT_REF_NAME}
|
||||
# Create manifest for regular image
|
||||
- |
|
||||
docker manifest create $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm-v7
|
||||
docker manifest push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
# Tag as latest on main branch or create version tags
|
||||
- |
|
||||
if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE:latest \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm-v7
|
||||
docker manifest push $CI_REGISTRY_IMAGE:latest
|
||||
fi
|
||||
- |
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
# Create version tag
|
||||
docker manifest create $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm-v7
|
||||
docker manifest push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
|
||||
|
||||
# Create major.minor tag if semantic version
|
||||
MAJOR_MINOR=$(echo $CI_COMMIT_TAG | sed -E 's/^v?([0-9]+\.[0-9]+)\..*/\1/')
|
||||
if [ -n "$MAJOR_MINOR" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE:$MAJOR_MINOR \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm64 \
|
||||
$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}-linux-arm-v7
|
||||
docker manifest push $CI_REGISTRY_IMAGE:$MAJOR_MINOR
|
||||
fi
|
||||
fi
|
||||
needs:
|
||||
- docker:build:amd64
|
||||
- docker:build:arm64
|
||||
- docker:build:armv7
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# ==========================================
|
||||
# Docker Manifest Creation - Rootless
|
||||
# ==========================================
|
||||
docker:manifest:rootless:
|
||||
stage: release
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
script:
|
||||
- export VERSION=${CI_COMMIT_TAG:-$CI_COMMIT_REF_NAME}
|
||||
# Create manifest for rootless image
|
||||
- |
|
||||
docker manifest create $CI_REGISTRY_IMAGE-rootless:$CI_COMMIT_REF_SLUG \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-rootless:$CI_COMMIT_REF_SLUG
|
||||
# Tag as latest on main branch or create version tags
|
||||
- |
|
||||
if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE-rootless:latest \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-rootless:latest
|
||||
fi
|
||||
- |
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE-rootless:$CI_COMMIT_TAG \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-rootless:$CI_COMMIT_TAG
|
||||
|
||||
MAJOR_MINOR=$(echo $CI_COMMIT_TAG | sed -E 's/^v?([0-9]+\.[0-9]+)\..*/\1/')
|
||||
if [ -n "$MAJOR_MINOR" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE-rootless:$MAJOR_MINOR \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-rootless:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-rootless:$MAJOR_MINOR
|
||||
fi
|
||||
fi
|
||||
needs:
|
||||
- docker:build:rootless:amd64
|
||||
- docker:build:rootless:arm64
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
|
||||
# ==========================================
|
||||
# Docker Manifest Creation - Hardened
|
||||
# ==========================================
|
||||
docker:manifest:hardened:
|
||||
stage: release
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
script:
|
||||
- export VERSION=${CI_COMMIT_TAG:-$CI_COMMIT_REF_NAME}
|
||||
# Create manifest for hardened image
|
||||
- |
|
||||
docker manifest create $CI_REGISTRY_IMAGE-hardened:$CI_COMMIT_REF_SLUG \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-hardened:$CI_COMMIT_REF_SLUG
|
||||
# Tag as latest on main branch or create version tags
|
||||
- |
|
||||
if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE-hardened:latest \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-hardened:latest
|
||||
fi
|
||||
- |
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE-hardened:$CI_COMMIT_TAG \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-hardened:$CI_COMMIT_TAG
|
||||
|
||||
MAJOR_MINOR=$(echo $CI_COMMIT_TAG | sed -E 's/^v?([0-9]+\.[0-9]+)\..*/\1/')
|
||||
if [ -n "$MAJOR_MINOR" ]; then
|
||||
docker manifest create $CI_REGISTRY_IMAGE-hardened:$MAJOR_MINOR \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-amd64 \
|
||||
$CI_REGISTRY_IMAGE-hardened:${CI_COMMIT_REF_SLUG}-linux-arm64
|
||||
docker manifest push $CI_REGISTRY_IMAGE-hardened:$MAJOR_MINOR
|
||||
fi
|
||||
fi
|
||||
needs:
|
||||
- docker:build:hardened:amd64
|
||||
- docker:build:hardened:arm64
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
14
.scaffold/go.sum
Normal file
@@ -0,0 +1,14 @@
|
||||
entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
|
||||
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
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-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=
|
||||
4
.vscode/settings.json
vendored
@@ -4,7 +4,7 @@
|
||||
},
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"package.json": "package-lock.json, yarn.lock, .eslintrc.js, tsconfig.json, .prettierrc, .editorconfig, pnpm-lock.yaml, postcss.config.js, tailwind.config.js",
|
||||
"package.json": "package-lock.json, yarn.lock, eslint.config.mjs, tsconfig.json, .prettierrc, .editorconfig, pnpm-lock.yaml, postcss.config.js, tailwind.config.js",
|
||||
"docker-compose.yml": "Dockerfile, .dockerignore, docker-compose.dev.yml, docker-compose.yml",
|
||||
"README.md": "LICENSE, SECURITY.md"
|
||||
},
|
||||
@@ -22,6 +22,8 @@
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"eslint.format.enable": true,
|
||||
"eslint.validate": ["javascript", "typescript", "vue"],
|
||||
"eslint.useFlatConfig": true,
|
||||
"css.validate": false,
|
||||
"tailwindCSS.includeLanguages": {
|
||||
"vue": "html",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Node dependencies stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally (caching layer)
|
||||
@@ -10,7 +10,7 @@ COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Build Nuxt (frontend) stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally again (it can reuse the cache if not changed)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ---------------------------------------
|
||||
# Node dependencies stage
|
||||
# ---------------------------------------
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally (caching layer)
|
||||
@@ -14,7 +14,7 @@ RUN pnpm install --frozen-lockfile
|
||||
# ---------------------------------------
|
||||
# Build Nuxt (frontend) stage
|
||||
# ---------------------------------------
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally again (it can reuse the cache if not changed)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Node dependencies stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-dependencies
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-dependencies
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally (caching layer)
|
||||
@@ -10,7 +10,7 @@ COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Build Nuxt (frontend) stage
|
||||
FROM public.ecr.aws/docker/library/node:lts-alpine AS frontend-builder
|
||||
FROM public.ecr.aws/docker/library/node:22-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally again (it can reuse the cache if not changed)
|
||||
|
||||
56
README.md
@@ -2,36 +2,59 @@
|
||||
<img src="/docs/public/lilbox.svg" height="200"/>
|
||||
</div>
|
||||
|
||||
<h1 align="center" style="margin-top: -10px"> HomeBox </h1>
|
||||
<p align="center" style="width: 100;">
|
||||
<h1 align="center" style="margin-top: -10px;"> HomeBox </h1>
|
||||
<p align="center" style="width: 100%;">
|
||||
<a href="https://homebox.software/en/">Docs</a>
|
||||
|
|
||||
<a href="https://demo.homebox.software">Demo</a>
|
||||
|
|
||||
<a href="https://discord.gg/aY4DCkpNA9">Discord</a>
|
||||
</p>
|
||||
<p align="center" style="width: 100%;">
|
||||
<img src="https://img.shields.io/github/check-runs/sysadminsmedia/homebox/main" alt="Github Checks"/>
|
||||
<img src="https://img.shields.io/github/license/sysadminsmedia/homebox"/>
|
||||
<img src="https://img.shields.io/github/v/release/sysadminsmedia/homebox?sort=semver&display_name=release"/>
|
||||
<img src="https://img.shields.io/weblate/progress/homebox?server=https%3A%2F%2Ftranslate.sysadminsmedia.com"/>
|
||||
</p>
|
||||
<p align="center" style="width: 100%;">
|
||||
<img src="https://img.shields.io/reddit/subreddit-subscribers/homebox"/>
|
||||
<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>
|
||||
|
||||
## What is HomeBox
|
||||
|
||||
HomeBox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind:
|
||||
HomeBox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, We've tried to keep the following principles in mind:
|
||||
|
||||
- _Simple_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. Use either a single docker container, or deploy yourself by compiling the binary for your platform of choice.
|
||||
- _Blazingly Fast_ - Homebox is written in Go, which makes it extremely fast and requires minimal resources to deploy. In general, idle memory usage is less than 50MB for the whole container.
|
||||
- _Portable_ - Homebox is designed to be portable and run on anywhere. We use SQLite and an embedded Web UI to make it easy to deploy, use, and backup.
|
||||
- 🧘 _Simple but Expandable_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. But expandable to whatever level of infrastructure you want to put into it.
|
||||
- 🚀 _Blazingly Fast_ - Homebox is written in Go, which makes it extremely fast and requires minimal resources to deploy. In general, idle memory usage is less than 50MB for the whole container.
|
||||
- 📦 _Portable_ - Homebox is designed to be portable and run on anywhere. We use SQLite and an embedded Web UI to make it easy to deploy, use, and backup.
|
||||
|
||||
### Key Features
|
||||
- 📇 Rich Organization - Organize your items into categories, locations, and tags. You can also create custom fields to store additional information about your items.
|
||||
- 🔍 Powerful Search - Quickly find items in your inventory using the powerful search feature.
|
||||
- 📸 Image Upload - Upload images of your items to make it easy to identify them.
|
||||
- 📄 Document and Warranty Tracking - Keep track of important documents and warranties for your items.
|
||||
- 💰 Purchase & Maintenance Tracking - Track purchase dates, prices, and maintenance schedules for your items.
|
||||
- 📱 Responsive Design - Homebox is designed to work on any device, including desktops, tablets, and smartphones.
|
||||
|
||||
## Screenshots
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Screenshots
|
||||
Check out screenshots of the project [here](https://imgur.com/a/5gLWt2j).
|
||||
You can also try the demo instances of Homebox:
|
||||
- [Demo](https://demo.homebox.software)
|
||||
- [Nightly](https://nightly.homebox.software)
|
||||
- [VNext](https://vnext.homebox.software/)
|
||||
|
||||
## Quick Start
|
||||
|
||||
[Configuration & Docker Compose](https://homebox.software/en/quick-start.html)
|
||||
|
||||
```bash
|
||||
# If using the rootless image, ensure data
|
||||
# If using the rootless or hardened image, ensure data
|
||||
# folder has correct permissions
|
||||
mkdir -p /path/to/data/folder
|
||||
chown 65532:65532 -R /path/to/data/folder
|
||||
@@ -43,6 +66,7 @@ docker run -d \
|
||||
--volume /path/to/data/folder/:/data \
|
||||
ghcr.io/sysadminsmedia/homebox:latest
|
||||
# ghcr.io/sysadminsmedia/homebox:latest-rootless
|
||||
# ghcr.io/sysadminsmedia/homebox:latest-hardened
|
||||
```
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
@@ -51,14 +75,20 @@ docker run -d \
|
||||
|
||||
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
||||
|
||||
If you are not a coder, you can still contribute financially. Financial contributions help me prioritize working on this project over others and helps me know that there is a real demand for project development.
|
||||
To get started with code based contributions, please see our [contributing guide](https://homebox.software/en/contribute/get-started.html).
|
||||
|
||||
If you are not a coder and can't help translate, you can still contribute financially. Financial contributions help us maintain the project and keep demos running.
|
||||
|
||||
## Help us Translate
|
||||
We want to make sure that Homebox is available in as many languages as possible. If you are interested in helping us translate Homebox, please help us via our [Weblate instance](https://translate.sysadminsmedia.com/projects/homebox/).
|
||||
|
||||
[](http://translate.sysadminsmedia.com/engage/homebox/)
|
||||
[](https://translate.sysadminsmedia.com/engage/homebox/)
|
||||
|
||||
## Credits
|
||||
|
||||
- Original project by [@hay-kot](https://github.com/hay-kot)
|
||||
- Logo by [@lakotelman](https://github.com/lakotelman)
|
||||
|
||||
### Contributors
|
||||
<a href="https://github.com/sysadminsmedia/homebox/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=sysadminsmedia/homebox" />
|
||||
</a>
|
||||
|
||||
24
Taskfile.yml
@@ -23,10 +23,13 @@ tasks:
|
||||
INTERNAL: "../../../internal"
|
||||
PKGS: "../../../pkgs"
|
||||
cmds:
|
||||
- swag fmt --dir={{ .API }}
|
||||
- swag init --dir={{ .API }},{{ .INTERNAL }}/core/services,{{ .INTERNAL }}/data/repo --parseDependency
|
||||
- cp -r ./docs/swagger.json ../../../../docs/en/api/openapi-2.0.json
|
||||
- cp -r ./docs/swagger.yaml ../../../../docs/en/api/openapi-2.0.yaml
|
||||
- npx -y -p swagger2openapi swagger2openapi --outfile ./docs/openapi-3.json ./docs/swagger.json
|
||||
- npx -y -p swagger2openapi swagger2openapi --yaml --outfile ./docs/openapi-3.yaml ./docs/swagger.json
|
||||
- cp -r ./docs/swagger.json ../../../../docs/en/api/swagger-2.0.json
|
||||
- cp -r ./docs/swagger.yaml ../../../../docs/en/api/swagger-2.0.yaml
|
||||
- cp -r ./docs/openapi-3.json ../../../../docs/en/api/openapi-3.0.json
|
||||
- cp -r ./docs/openapi-3.yaml ../../../../docs/en/api/openapi-3.0.yaml
|
||||
sources:
|
||||
- "./backend/app/api/**/*"
|
||||
- "./backend/internal/data/**"
|
||||
@@ -93,6 +96,16 @@ tasks:
|
||||
- go run ./app/api/ {{ .CLI_ARGS }} &
|
||||
silent: true
|
||||
|
||||
go:ci:with-frontend:
|
||||
desc: Run backend with frontend in CI mode
|
||||
dir: frontend
|
||||
cmds:
|
||||
- pnpm install
|
||||
- pnpm run build
|
||||
- cp -r ./.output/public ../backend/app/api/static/
|
||||
- task: go:ci
|
||||
silent: true
|
||||
|
||||
go:test:
|
||||
desc: Runs all go tests using gotestsum - supports passing gotestsum args
|
||||
dir: backend
|
||||
@@ -201,12 +214,11 @@ tasks:
|
||||
desc: Runs end-to-end test on a live server
|
||||
dir: frontend
|
||||
cmds:
|
||||
- task: go:ci
|
||||
- task: ui:ci
|
||||
- task: go:ci:with-frontend
|
||||
- pnpm exec playwright install-deps
|
||||
- pnpm exec playwright install
|
||||
- sleep 30
|
||||
- TEST_SHUTDOWN_API_SERVER=true pnpm exec playwright test -c ./test/playwright.config.ts {{ .CLI_ARGS }}
|
||||
- TEST_SHUTDOWN_API_SERVER=true E2E_BASE_URL=http://localhost:7745 pnpm exec playwright test -c ./test/playwright.config.ts {{ .CLI_ARGS }}
|
||||
|
||||
pr:
|
||||
desc: Runs all tasks required for a PR
|
||||
|
||||
@@ -17,38 +17,31 @@ builds:
|
||||
- freebsd
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: "386"
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
- goos: freebsd
|
||||
goarch: "386"
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X main.version={{.Version}}
|
||||
- -X main.commit={{.Commit}}
|
||||
- -X main.date={{.Date}}
|
||||
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 }}
|
||||
|
||||
signs:
|
||||
- cmd: cosign
|
||||
stdin: "{{ .Env.COSIGN_PWD }}"
|
||||
signature: "${artifact}.sigstore.json"
|
||||
args:
|
||||
- "sign-blob"
|
||||
- "--key=cosign.key"
|
||||
- "--output-signature=${signature}"
|
||||
- sign-blob
|
||||
- "--bundle=${signature}"
|
||||
- "${artifact}"
|
||||
- "--yes" # needed on cosign 2.0.0+
|
||||
artifacts: all
|
||||
|
||||
- "--yes"
|
||||
artifacts: checksum
|
||||
output: true
|
||||
archives:
|
||||
- formats: [ 'tar.gz' ]
|
||||
# this name template makes the OS and Arch compatible with the results of uname.
|
||||
@@ -56,14 +49,14 @@ 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
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [ 'zip' ]
|
||||
|
||||
sboms:
|
||||
- artifacts: archive
|
||||
release:
|
||||
extra_files:
|
||||
- glob: dist/*.sig
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/hay-kot/httpkit/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/sysadminsmedia/homebox/backend/app/api/providers"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services/reporting/eventbus"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
@@ -74,6 +75,7 @@ type V1Controller struct {
|
||||
bus *eventbus.EventBus
|
||||
url string
|
||||
config *config.Config
|
||||
oidcProvider *providers.OIDCProvider
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -95,6 +97,14 @@ type (
|
||||
Demo bool `json:"demo"`
|
||||
AllowRegistration bool `json:"allowRegistration"`
|
||||
LabelPrinting bool `json:"labelPrinting"`
|
||||
OIDC OIDCStatus `json:"oidc"`
|
||||
}
|
||||
|
||||
OIDCStatus struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ButtonText string `json:"buttonText,omitempty"`
|
||||
AutoRedirect bool `json:"autoRedirect,omitempty"`
|
||||
AllowLocal bool `json:"allowLocal"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -111,9 +121,23 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *event
|
||||
opt(ctrl)
|
||||
}
|
||||
|
||||
ctrl.initOIDCProvider()
|
||||
|
||||
return ctrl
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) initOIDCProvider() {
|
||||
if ctrl.config.OIDC.Enabled {
|
||||
oidcProvider, err := providers.NewOIDCProvider(ctrl.svc.User, &ctrl.config.OIDC, &ctrl.config.Options, ctrl.cookieSecure)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to initialize OIDC provider at startup")
|
||||
} else {
|
||||
ctrl.oidcProvider = oidcProvider
|
||||
log.Info().Msg("OIDC provider initialized successfully at startup")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HandleBase godoc
|
||||
//
|
||||
// @Summary Application Info
|
||||
@@ -132,6 +156,12 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand
|
||||
Demo: ctrl.isDemo,
|
||||
AllowRegistration: ctrl.allowRegistration,
|
||||
LabelPrinting: ctrl.config.LabelMaker.PrintCommand != nil,
|
||||
OIDC: OIDCStatus{
|
||||
Enabled: ctrl.config.OIDC.Enabled,
|
||||
ButtonText: ctrl.config.OIDC.ButtonText,
|
||||
AutoRedirect: ctrl.config.OIDC.AutoRedirect,
|
||||
AllowLocal: ctrl.config.Options.AllowLocalLogin,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
WipeLabels bool `json:"wipeLabels"`
|
||||
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{
|
||||
WipeLabels: false,
|
||||
WipeLocations: false,
|
||||
WipeMaintenance: false,
|
||||
}
|
||||
}
|
||||
|
||||
totalCompleted, err := ctrl.repo.Items.WipeInventory(ctx, ctx.GID, options.WipeLabels, 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.WipeLabels {
|
||||
ctrl.bus.Publish(eventbus.EventLabelMutation, 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})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -106,6 +107,11 @@ func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFu
|
||||
provider = "local"
|
||||
}
|
||||
|
||||
// Block local only when disabled
|
||||
if provider == "local" && !ctrl.config.Options.AllowLocalLogin {
|
||||
return validate.NewRequestError(fmt.Errorf("local login is not enabled"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
// Get the provider
|
||||
p, ok := providers[provider]
|
||||
if !ok {
|
||||
@@ -114,11 +120,11 @@ func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFu
|
||||
|
||||
newToken, err := p.Authenticate(w, r)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to authenticate")
|
||||
return server.JSON(w, http.StatusInternalServerError, err.Error())
|
||||
log.Warn().Err(err).Msg("authentication failed")
|
||||
return validate.NewUnauthorizedError()
|
||||
}
|
||||
|
||||
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true)
|
||||
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true, newToken.AttachmentToken)
|
||||
return server.JSON(w, http.StatusOK, TokenResponse{
|
||||
Token: "Bearer " + newToken.Raw,
|
||||
ExpiresAt: newToken.ExpiresAt,
|
||||
@@ -172,7 +178,7 @@ func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc {
|
||||
return validate.NewUnauthorizedError()
|
||||
}
|
||||
|
||||
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, false)
|
||||
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, false, newToken.AttachmentToken)
|
||||
return server.JSON(w, http.StatusOK, newToken)
|
||||
}
|
||||
}
|
||||
@@ -181,7 +187,7 @@ func noPort(host string) string {
|
||||
return strings.Split(host, ":")[0]
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) setCookies(w http.ResponseWriter, domain, token string, expires time.Time, remember bool) {
|
||||
func (ctrl *V1Controller) setCookies(w http.ResponseWriter, domain, token string, expires time.Time, remember bool, attachmentToken string) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieNameRemember,
|
||||
Value: strconv.FormatBool(remember),
|
||||
@@ -213,6 +219,19 @@ func (ctrl *V1Controller) setCookies(w http.ResponseWriter, domain, token string
|
||||
HttpOnly: false,
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
// Set attachment token cookie (accessible to frontend, not HttpOnly)
|
||||
if attachmentToken != "" {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "hb.auth.attachment_token",
|
||||
Value: attachmentToken,
|
||||
Expires: expires,
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: false,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *V1Controller) unsetCookies(w http.ResponseWriter, domain string) {
|
||||
@@ -246,4 +265,77 @@ func (ctrl *V1Controller) unsetCookies(w http.ResponseWriter, domain string) {
|
||||
HttpOnly: false,
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
// Unset attachment token cookie
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "hb.auth.attachment_token",
|
||||
Value: "",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: domain,
|
||||
Secure: ctrl.cookieSecure,
|
||||
HttpOnly: false,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
|
||||
// HandleOIDCLogin godoc
|
||||
//
|
||||
// @Summary OIDC Login Initiation
|
||||
// @Tags Authentication
|
||||
// @Produce json
|
||||
// @Success 302
|
||||
// @Router /v1/users/login/oidc [GET]
|
||||
func (ctrl *V1Controller) HandleOIDCLogin() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
// Forbidden if OIDC is not enabled
|
||||
if !ctrl.config.OIDC.Enabled {
|
||||
return validate.NewRequestError(fmt.Errorf("OIDC is not enabled"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
// Check if OIDC provider is available
|
||||
if ctrl.oidcProvider == nil {
|
||||
log.Error().Msg("OIDC provider not initialized")
|
||||
return validate.NewRequestError(errors.New("OIDC provider not available"), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Initiate OIDC flow
|
||||
_, err := ctrl.oidcProvider.InitiateOIDCFlow(w, r)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// HandleOIDCCallback godoc
|
||||
//
|
||||
// @Summary OIDC Callback Handler
|
||||
// @Tags Authentication
|
||||
// @Param code query string true "Authorization code"
|
||||
// @Param state query string true "State parameter"
|
||||
// @Success 302
|
||||
// @Router /v1/users/login/oidc/callback [GET]
|
||||
func (ctrl *V1Controller) HandleOIDCCallback() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
// Forbidden if OIDC is not enabled
|
||||
if !ctrl.config.OIDC.Enabled {
|
||||
return validate.NewRequestError(fmt.Errorf("OIDC is not enabled"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
// Check if OIDC provider is available
|
||||
if ctrl.oidcProvider == nil {
|
||||
log.Error().Msg("OIDC provider not initialized")
|
||||
return validate.NewRequestError(errors.New("OIDC provider not available"), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Handle callback
|
||||
newToken, err := ctrl.oidcProvider.HandleCallback(w, r)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("OIDC callback failed")
|
||||
http.Redirect(w, r, "/?oidc_error=oidc_auth_failed", http.StatusFound)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set cookies and redirect to home
|
||||
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true, newToken.AttachmentToken)
|
||||
http.Redirect(w, r, "/home", http.StatusFound)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
164
backend/app/api/handlers/v1/v1_ctrl_item_templates.go
Normal file
@@ -0,0 +1,164 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// HandleItemTemplatesGetAll godoc
|
||||
//
|
||||
// @Summary Get All Item Templates
|
||||
// @Tags Item Templates
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.ItemTemplateSummary
|
||||
// @Router /v1/templates [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemTemplatesGetAll() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]repo.ItemTemplateSummary, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.ItemTemplates.GetAll(r.Context(), auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleItemTemplatesGet godoc
|
||||
//
|
||||
// @Summary Get Item Template
|
||||
// @Tags Item Templates
|
||||
// @Produce json
|
||||
// @Param id path string true "Template ID"
|
||||
// @Success 200 {object} repo.ItemTemplateOut
|
||||
// @Router /v1/templates/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemTemplatesGet() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (repo.ItemTemplateOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.ItemTemplates.GetOne(r.Context(), auth.GID, ID)
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleItemTemplatesCreate godoc
|
||||
//
|
||||
// @Summary Create Item Template
|
||||
// @Tags Item Templates
|
||||
// @Produce json
|
||||
// @Param payload body repo.ItemTemplateCreate true "Template Data"
|
||||
// @Success 201 {object} repo.ItemTemplateOut
|
||||
// @Router /v1/templates [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemTemplatesCreate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, body repo.ItemTemplateCreate) (repo.ItemTemplateOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.ItemTemplates.Create(r.Context(), auth.GID, body)
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleItemTemplatesUpdate godoc
|
||||
//
|
||||
// @Summary Update Item Template
|
||||
// @Tags Item Templates
|
||||
// @Produce json
|
||||
// @Param id path string true "Template ID"
|
||||
// @Param payload body repo.ItemTemplateUpdate true "Template Data"
|
||||
// @Success 200 {object} repo.ItemTemplateOut
|
||||
// @Router /v1/templates/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemTemplatesUpdate() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID, body repo.ItemTemplateUpdate) (repo.ItemTemplateOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
body.ID = ID
|
||||
return ctrl.repo.ItemTemplates.Update(r.Context(), auth.GID, body)
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleItemTemplatesDelete godoc
|
||||
//
|
||||
// @Summary Delete Item Template
|
||||
// @Tags Item Templates
|
||||
// @Produce json
|
||||
// @Param id path string true "Template ID"
|
||||
// @Success 204
|
||||
// @Router /v1/templates/{id} [DELETE]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemTemplatesDelete() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.repo.ItemTemplates.Delete(r.Context(), auth.GID, ID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
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"`
|
||||
Quantity *int `json:"quantity"`
|
||||
}
|
||||
|
||||
// HandleItemTemplatesCreateItem godoc
|
||||
//
|
||||
// @Summary Create Item from Template
|
||||
// @Tags Item Templates
|
||||
// @Produce json
|
||||
// @Param id path string true "Template ID"
|
||||
// @Param payload body ItemTemplateCreateItemRequest true "Item Data"
|
||||
// @Success 201 {object} repo.ItemOut
|
||||
// @Router /v1/templates/{id}/create-item [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemTemplatesCreateItem() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, templateID uuid.UUID, body ItemTemplateCreateItemRequest) (repo.ItemOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
|
||||
template, err := ctrl.repo.ItemTemplates.GetOne(r.Context(), auth.GID, templateID)
|
||||
if err != nil {
|
||||
return repo.ItemOut{}, err
|
||||
}
|
||||
|
||||
quantity := template.DefaultQuantity
|
||||
if body.Quantity != nil {
|
||||
quantity = *body.Quantity
|
||||
}
|
||||
|
||||
// Build custom fields from template
|
||||
fields := make([]repo.ItemField, len(template.Fields))
|
||||
for i, f := range template.Fields {
|
||||
fields[i] = repo.ItemField{
|
||||
Type: f.Type,
|
||||
Name: f.Name,
|
||||
TextValue: f.TextValue,
|
||||
}
|
||||
}
|
||||
|
||||
// Create item with all template data in a single transaction
|
||||
return ctrl.repo.Items.CreateFromTemplate(r.Context(), auth.GID, repo.ItemCreateFromTemplate{
|
||||
Name: body.Name,
|
||||
Description: body.Description,
|
||||
Quantity: quantity,
|
||||
LocationID: body.LocationID,
|
||||
LabelIDs: body.LabelIDs,
|
||||
Insured: template.DefaultInsured,
|
||||
Manufacturer: template.DefaultManufacturer,
|
||||
ModelNumber: template.DefaultModelNumber,
|
||||
LifetimeWarranty: template.DefaultLifetimeWarranty,
|
||||
WarrantyDetails: template.DefaultWarrantyDetails,
|
||||
Fields: fields,
|
||||
})
|
||||
}
|
||||
|
||||
return adapters.ActionID("id", fn, http.StatusCreated)
|
||||
}
|
||||
@@ -186,7 +186,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
|
||||
log.Err(err).Msg("failed to open bucket")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
file, err := bucket.NewReader(ctx, doc.Path, nil)
|
||||
file, err := bucket.NewReader(ctx, ctrl.repo.Attachments.GetFullPath(doc.Path), nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to open file")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
|
||||
@@ -9,7 +9,6 @@ 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/data/ent/schema"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/validate"
|
||||
)
|
||||
@@ -21,9 +20,15 @@ import (
|
||||
// @Produce json
|
||||
// @Param payload body services.UserRegistration true "User Data"
|
||||
// @Success 204
|
||||
// @Failure 403 {string} string "Local login is not enabled"
|
||||
// @Router /v1/users/register [Post]
|
||||
func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
// Forbidden if local login is not enabled
|
||||
if !ctrl.config.Options.AllowLocalLogin {
|
||||
return validate.NewRequestError(fmt.Errorf("local login is not enabled"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
regData := services.UserRegistration{}
|
||||
|
||||
if err := server.Decode(r, ®Data); err != nil {
|
||||
@@ -116,60 +121,6 @@ func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// HandleUserSelfSettingsGet godoc
|
||||
//
|
||||
// @Summary Get user settings
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Success 200 {object} Wrapped{item=schema.UserSettings}
|
||||
// @Router /v1/users/self/settings [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelfSettingsGet() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
actor := services.UseUserCtx(r.Context())
|
||||
settings, err := ctrl.svc.User.GetSettings(r.Context(), actor.ID)
|
||||
if err != nil {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
return server.JSON(w, http.StatusOK, Wrap(settings))
|
||||
}
|
||||
}
|
||||
|
||||
// HandleUserSelfSettingsUpdate godoc
|
||||
//
|
||||
// @Summary Update user settings
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Success 200 {object} Wrapped{item=schema.UserSettings}
|
||||
// @Router /v1/users/self/settings [PUT]
|
||||
// @Param payload body schema.UserSettings true "Settings Data"
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelfSettingsUpdate() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
// Cap body to prevent DOS via large payloads.
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 64*1024)
|
||||
var settings schema.UserSettings
|
||||
if err := server.Decode(r, &settings); err != nil {
|
||||
log.Err(err).Msg("failed to decode user settings data")
|
||||
return validate.NewRequestError(err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
actor := services.UseUserCtx(r.Context())
|
||||
if err := ctrl.svc.User.SetSettings(r.Context(), actor.ID, settings); err != nil {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
newSettings, err := ctrl.svc.User.GetSettings(r.Context(), actor.ID)
|
||||
if err != nil {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
return server.JSON(w, http.StatusOK, Wrap(newSettings))
|
||||
}
|
||||
}
|
||||
type (
|
||||
ChangePassword struct {
|
||||
Current string `json:"current,omitempty"`
|
||||
|
||||
@@ -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)
|
||||
|
||||
626
backend/app/api/providers/oidc.go
Normal file
@@ -0,0 +1,626 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type OIDCProvider struct {
|
||||
service *services.UserService
|
||||
config *config.OIDCConf
|
||||
options *config.Options
|
||||
cookieSecure bool
|
||||
provider *oidc.Provider
|
||||
verifier *oidc.IDTokenVerifier
|
||||
endpoint oauth2.Endpoint
|
||||
}
|
||||
|
||||
type OIDCClaims struct {
|
||||
Email string
|
||||
Groups []string
|
||||
Name string
|
||||
Subject string
|
||||
Issuer string
|
||||
EmailVerified *bool
|
||||
}
|
||||
|
||||
func NewOIDCProvider(service *services.UserService, config *config.OIDCConf, options *config.Options, cookieSecure bool) (*OIDCProvider, error) {
|
||||
if !config.Enabled {
|
||||
return nil, fmt.Errorf("OIDC is not enabled")
|
||||
}
|
||||
|
||||
// Validate required configuration
|
||||
if config.ClientID == "" {
|
||||
return nil, fmt.Errorf("OIDC client ID is required when OIDC is enabled (set HBOX_OIDC_CLIENT_ID)")
|
||||
}
|
||||
if config.ClientSecret == "" {
|
||||
return nil, fmt.Errorf("OIDC client secret is required when OIDC is enabled (set HBOX_OIDC_CLIENT_SECRET)")
|
||||
}
|
||||
if config.IssuerURL == "" {
|
||||
return nil, fmt.Errorf("OIDC issuer URL is required when OIDC is enabled (set HBOX_OIDC_ISSUER_URL)")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), config.RequestTimeout)
|
||||
defer cancel()
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, config.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create OIDC provider from issuer URL: %w", err)
|
||||
}
|
||||
|
||||
// Create ID token verifier
|
||||
verifier := provider.Verifier(&oidc.Config{
|
||||
ClientID: config.ClientID,
|
||||
})
|
||||
|
||||
log.Info().
|
||||
Str("issuer", config.IssuerURL).
|
||||
Str("client_id", config.ClientID).
|
||||
Str("scope", config.Scope).
|
||||
Msg("OIDC provider initialized successfully with discovery")
|
||||
|
||||
return &OIDCProvider{
|
||||
service: service,
|
||||
config: config,
|
||||
options: options,
|
||||
cookieSecure: cookieSecure,
|
||||
provider: provider,
|
||||
verifier: verifier,
|
||||
endpoint: provider.Endpoint(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) Name() string {
|
||||
return "oidc"
|
||||
}
|
||||
|
||||
// Authenticate implements the AuthProvider interface but is not used for OIDC
|
||||
// OIDC uses dedicated endpoints: GET /api/v1/users/login/oidc and GET /api/v1/users/login/oidc/callback
|
||||
func (p *OIDCProvider) Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
|
||||
_ = w
|
||||
_ = r
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("OIDC authentication uses dedicated endpoints: /api/v1/users/login/oidc")
|
||||
}
|
||||
|
||||
// AuthenticateWithBaseURL is the main authentication method that requires baseURL
|
||||
// Called from handleCallback after state, nonce, and PKCE verification
|
||||
func (p *OIDCProvider) AuthenticateWithBaseURL(baseURL, expectedNonce, pkceVerifier string, _ http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
|
||||
code := r.URL.Query().Get("code")
|
||||
if code == "" {
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("missing authorization code")
|
||||
}
|
||||
|
||||
// Get OAuth2 config for this request
|
||||
oauth2Config := p.getOAuth2Config(baseURL)
|
||||
|
||||
// Exchange code for token with timeout and PKCE verifier
|
||||
ctx, cancel := context.WithTimeout(r.Context(), p.config.RequestTimeout)
|
||||
defer cancel()
|
||||
|
||||
token, err := oauth2Config.Exchange(ctx, code, oauth2.SetAuthURLParam("code_verifier", pkceVerifier))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to exchange OIDC code for token")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("failed to exchange code for token")
|
||||
}
|
||||
|
||||
// Extract ID token
|
||||
idToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("no id_token in response")
|
||||
}
|
||||
|
||||
// Parse and validate the ID token using the library's verifier with timeout
|
||||
verifyCtx, verifyCancel := context.WithTimeout(r.Context(), p.config.RequestTimeout)
|
||||
defer verifyCancel()
|
||||
|
||||
idTokenStruct, err := p.verifier.Verify(verifyCtx, idToken)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to verify ID token")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("failed to verify ID token")
|
||||
}
|
||||
|
||||
// Extract claims from the verified token using dynamic parsing
|
||||
var rawClaims map[string]interface{}
|
||||
if err := idTokenStruct.Claims(&rawClaims); err != nil {
|
||||
log.Err(err).Msg("failed to extract claims from ID token")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("failed to extract claims from ID token")
|
||||
}
|
||||
|
||||
// Attempt to retrieve UserInfo claims; use them as primary, fallback to ID token claims.
|
||||
finalClaims := rawClaims
|
||||
userInfoCtx, uiCancel := context.WithTimeout(r.Context(), p.config.RequestTimeout)
|
||||
defer uiCancel()
|
||||
|
||||
userInfo, uiErr := p.provider.UserInfo(userInfoCtx, oauth2.StaticTokenSource(token))
|
||||
if uiErr != nil {
|
||||
log.Debug().Err(uiErr).Msg("OIDC UserInfo fetch failed; falling back to ID token claims")
|
||||
} else {
|
||||
var uiClaims map[string]interface{}
|
||||
if err := userInfo.Claims(&uiClaims); err != nil {
|
||||
log.Debug().Err(err).Msg("failed to decode UserInfo claims; falling back to ID token claims")
|
||||
} else {
|
||||
finalClaims = mergeOIDCClaims(uiClaims, rawClaims) // UserInfo first, then fill gaps from ID token
|
||||
log.Debug().Int("userinfo_claims", len(uiClaims)).Int("id_token_claims", len(rawClaims)).Int("merged_claims", len(finalClaims)).Msg("merged UserInfo and ID token claims")
|
||||
}
|
||||
}
|
||||
|
||||
// Parse claims using configurable claim names (after merge)
|
||||
claims, err := p.parseOIDCClaims(finalClaims)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse OIDC claims")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("failed to parse OIDC claims: %w", err)
|
||||
}
|
||||
|
||||
// Verify nonce claim matches expected value (nonce only from ID token; ensure preserved in merged map)
|
||||
tokenNonce, exists := finalClaims["nonce"]
|
||||
if !exists {
|
||||
log.Warn().Msg("nonce claim missing from ID token - possible replay attack")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("nonce claim missing from token")
|
||||
}
|
||||
|
||||
tokenNonceStr, ok := tokenNonce.(string)
|
||||
if !ok {
|
||||
log.Warn().Msg("nonce claim is not a string in ID token")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("invalid nonce claim format")
|
||||
}
|
||||
|
||||
if tokenNonceStr != expectedNonce {
|
||||
log.Warn().Str("received", tokenNonceStr).Str("expected", expectedNonce).Msg("OIDC nonce mismatch - possible replay attack")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("nonce parameter mismatch")
|
||||
}
|
||||
|
||||
// Check if email is verified
|
||||
if p.config.VerifyEmail {
|
||||
if claims.EmailVerified == nil {
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("email verification status not found in token claims")
|
||||
}
|
||||
|
||||
if !*claims.EmailVerified {
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("email not verified")
|
||||
}
|
||||
}
|
||||
|
||||
// Check group authorization if configured
|
||||
if p.config.AllowedGroups != "" {
|
||||
allowedGroups := strings.Split(p.config.AllowedGroups, ",")
|
||||
if !p.hasAllowedGroup(claims.Groups, allowedGroups) {
|
||||
log.Warn().
|
||||
Strs("user_groups", claims.Groups).
|
||||
Strs("allowed_groups", allowedGroups).
|
||||
Str("user", claims.Email).
|
||||
Msg("user not in allowed groups")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("user not in allowed groups")
|
||||
}
|
||||
}
|
||||
|
||||
// Determine username from claims
|
||||
email := claims.Email
|
||||
if email == "" {
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("no email found in token claims")
|
||||
}
|
||||
if claims.Subject == "" {
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("no subject (sub) claim present")
|
||||
}
|
||||
if claims.Issuer == "" {
|
||||
claims.Issuer = p.config.IssuerURL // fallback to configured issuer, though spec requires 'iss'
|
||||
}
|
||||
|
||||
// Use the dedicated OIDC login method (issuer + subject identity)
|
||||
sessionToken, err := p.service.LoginOIDC(r.Context(), claims.Issuer, claims.Subject, email, claims.Name)
|
||||
if err != nil {
|
||||
log.Err(err).Str("email", email).Str("issuer", claims.Issuer).Str("subject", claims.Subject).Msg("OIDC login failed")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("OIDC login failed: %w", err)
|
||||
}
|
||||
|
||||
return sessionToken, nil
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) parseOIDCClaims(rawClaims map[string]interface{}) (OIDCClaims, error) {
|
||||
var claims OIDCClaims
|
||||
|
||||
// Parse email claim
|
||||
key := p.config.EmailClaim
|
||||
if key == "" {
|
||||
key = "email"
|
||||
}
|
||||
if emailValue, exists := rawClaims[key]; exists {
|
||||
if email, ok := emailValue.(string); ok {
|
||||
claims.Email = email
|
||||
}
|
||||
}
|
||||
|
||||
// Parse email_verified claim
|
||||
if p.config.VerifyEmail {
|
||||
key = p.config.EmailVerifiedClaim
|
||||
if key == "" {
|
||||
key = "email_verified"
|
||||
}
|
||||
if emailVerifiedValue, exists := rawClaims[key]; exists {
|
||||
switch v := emailVerifiedValue.(type) {
|
||||
case bool:
|
||||
claims.EmailVerified = &v
|
||||
case string:
|
||||
if b, err := strconv.ParseBool(v); err == nil {
|
||||
claims.EmailVerified = &b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse name claim
|
||||
key = p.config.NameClaim
|
||||
if key == "" {
|
||||
key = "name"
|
||||
}
|
||||
if nameValue, exists := rawClaims[key]; exists {
|
||||
if name, ok := nameValue.(string); ok {
|
||||
claims.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// Parse groups claim
|
||||
key = p.config.GroupClaim
|
||||
if key == "" {
|
||||
key = "groups"
|
||||
}
|
||||
if groupsValue, exists := rawClaims[key]; exists {
|
||||
switch groups := groupsValue.(type) {
|
||||
case []interface{}:
|
||||
for _, group := range groups {
|
||||
if groupStr, ok := group.(string); ok {
|
||||
claims.Groups = append(claims.Groups, groupStr)
|
||||
}
|
||||
}
|
||||
case []string:
|
||||
claims.Groups = groups
|
||||
case string:
|
||||
// Single group as string
|
||||
claims.Groups = []string{groups}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse subject claim (always "sub")
|
||||
if subValue, exists := rawClaims["sub"]; exists {
|
||||
if subject, ok := subValue.(string); ok {
|
||||
claims.Subject = subject
|
||||
}
|
||||
}
|
||||
// Parse issuer claim ("iss")
|
||||
if issValue, exists := rawClaims["iss"]; exists {
|
||||
if iss, ok := issValue.(string); ok {
|
||||
claims.Issuer = iss
|
||||
}
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) hasAllowedGroup(userGroups, allowedGroups []string) bool {
|
||||
if len(allowedGroups) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
allowedGroupsMap := make(map[string]bool)
|
||||
for _, group := range allowedGroups {
|
||||
allowedGroupsMap[strings.TrimSpace(group)] = true
|
||||
}
|
||||
|
||||
for _, userGroup := range userGroups {
|
||||
if allowedGroupsMap[userGroup] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) GetAuthURL(baseURL, state, nonce, pkceVerifier string) string {
|
||||
oauth2Config := p.getOAuth2Config(baseURL)
|
||||
pkceChallenge := generatePKCEChallenge(pkceVerifier)
|
||||
return oauth2Config.AuthCodeURL(state,
|
||||
oidc.Nonce(nonce),
|
||||
oauth2.SetAuthURLParam("code_challenge", pkceChallenge),
|
||||
oauth2.SetAuthURLParam("code_challenge_method", "S256"))
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) getOAuth2Config(baseURL string) oauth2.Config {
|
||||
// Construct full redirect URL with dedicated callback endpoint
|
||||
redirectURL, err := url.JoinPath(baseURL, "/api/v1/users/login/oidc/callback")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to construct redirect URL")
|
||||
return oauth2.Config{}
|
||||
}
|
||||
|
||||
return oauth2.Config{
|
||||
ClientID: p.config.ClientID,
|
||||
ClientSecret: p.config.ClientSecret,
|
||||
RedirectURL: redirectURL,
|
||||
Endpoint: p.endpoint,
|
||||
Scopes: strings.Fields(p.config.Scope),
|
||||
}
|
||||
}
|
||||
|
||||
// initiateOIDCFlow handles the initial OIDC authentication request by redirecting to the provider
|
||||
func (p *OIDCProvider) initiateOIDCFlow(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
|
||||
// Generate state parameter for CSRF protection
|
||||
state, err := generateSecureToken()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to generate OIDC state parameter")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("internal server error")
|
||||
}
|
||||
|
||||
// Generate nonce parameter for replay attack protection
|
||||
nonce, err := generateSecureToken()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to generate OIDC nonce parameter")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("internal server error")
|
||||
}
|
||||
|
||||
// Generate PKCE verifier for code interception protection
|
||||
pkceVerifier, err := generatePKCEVerifier()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to generate OIDC PKCE verifier")
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("internal server error")
|
||||
}
|
||||
|
||||
// Get base URL from request
|
||||
baseURL := p.getBaseURL(r)
|
||||
u, _ := url.Parse(baseURL)
|
||||
domain := u.Hostname()
|
||||
if domain == "" {
|
||||
domain = noPort(r.Host)
|
||||
}
|
||||
|
||||
// Store state in session cookie for validation
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "oidc_state",
|
||||
Value: state,
|
||||
Expires: time.Now().Add(p.config.StateExpiry),
|
||||
Domain: domain,
|
||||
Secure: p.isSecure(r),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
|
||||
// Store nonce in session cookie for validation
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "oidc_nonce",
|
||||
Value: nonce,
|
||||
Expires: time.Now().Add(p.config.StateExpiry),
|
||||
Domain: domain,
|
||||
Secure: p.isSecure(r),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
|
||||
// Store PKCE verifier in session cookie for token exchange
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "oidc_pkce_verifier",
|
||||
Value: pkceVerifier,
|
||||
Expires: time.Now().Add(p.config.StateExpiry),
|
||||
Domain: domain,
|
||||
Secure: p.isSecure(r),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
|
||||
// Generate auth URL and redirect
|
||||
authURL := p.GetAuthURL(baseURL, state, nonce, pkceVerifier)
|
||||
http.Redirect(w, r, authURL, http.StatusFound)
|
||||
|
||||
// Return empty token since this is a redirect response
|
||||
return services.UserAuthTokenDetail{}, nil
|
||||
}
|
||||
|
||||
// handleCallback processes the OAuth2 callback from the OIDC provider
|
||||
func (p *OIDCProvider) handleCallback(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
|
||||
// Helper to clear state cookie using computed domain
|
||||
baseURL := p.getBaseURL(r)
|
||||
u, _ := url.Parse(baseURL)
|
||||
domain := u.Hostname()
|
||||
if domain == "" {
|
||||
domain = noPort(r.Host)
|
||||
}
|
||||
clearCookies := func() {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "oidc_state",
|
||||
Value: "",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: domain,
|
||||
MaxAge: -1,
|
||||
Secure: p.isSecure(r),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "oidc_nonce",
|
||||
Value: "",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: domain,
|
||||
MaxAge: -1,
|
||||
Secure: p.isSecure(r),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "oidc_pkce_verifier",
|
||||
Value: "",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: domain,
|
||||
MaxAge: -1,
|
||||
Secure: p.isSecure(r),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
||||
// Check for OAuth error responses first
|
||||
if errCode := r.URL.Query().Get("error"); errCode != "" {
|
||||
errDesc := r.URL.Query().Get("error_description")
|
||||
log.Warn().Str("error", errCode).Str("description", errDesc).Msg("OIDC provider returned error")
|
||||
clearCookies()
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("OIDC provider error: %s - %s", errCode, errDesc)
|
||||
}
|
||||
|
||||
// Verify state parameter
|
||||
stateCookie, err := r.Cookie("oidc_state")
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("OIDC state cookie not found - possible CSRF attack or expired session")
|
||||
clearCookies()
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("state cookie not found")
|
||||
}
|
||||
|
||||
stateParam := r.URL.Query().Get("state")
|
||||
if stateParam == "" {
|
||||
log.Warn().Msg("OIDC state parameter missing from callback")
|
||||
clearCookies()
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("state parameter missing")
|
||||
}
|
||||
|
||||
if stateParam != stateCookie.Value {
|
||||
log.Warn().Str("received", stateParam).Str("expected", stateCookie.Value).Msg("OIDC state mismatch - possible CSRF attack")
|
||||
clearCookies()
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("state parameter mismatch")
|
||||
}
|
||||
|
||||
// Verify nonce parameter
|
||||
nonceCookie, err := r.Cookie("oidc_nonce")
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("OIDC nonce cookie not found - possible replay attack or expired session")
|
||||
clearCookies()
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("nonce cookie not found")
|
||||
}
|
||||
|
||||
// Verify PKCE verifier parameter
|
||||
pkceCookie, err := r.Cookie("oidc_pkce_verifier")
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("OIDC PKCE verifier cookie not found - possible code interception attack or expired session")
|
||||
clearCookies()
|
||||
return services.UserAuthTokenDetail{}, fmt.Errorf("PKCE verifier cookie not found")
|
||||
}
|
||||
|
||||
// Clear cookies before proceeding to token verification
|
||||
clearCookies()
|
||||
|
||||
// Use the existing callback logic but return the token instead of redirecting
|
||||
return p.AuthenticateWithBaseURL(baseURL, nonceCookie.Value, pkceCookie.Value, w, r)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func generateSecureToken() (string, error) {
|
||||
// Generate 32 bytes of cryptographically secure random data
|
||||
bytes := make([]byte, 32)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate secure random token: %w", err)
|
||||
}
|
||||
// Use URL-safe base64 encoding without padding for clean URLs
|
||||
return base64.RawURLEncoding.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
// generatePKCEVerifier generates a cryptographically secure code verifier for PKCE
|
||||
func generatePKCEVerifier() (string, error) {
|
||||
// PKCE verifier must be 43-128 characters, we'll use 43 for efficiency
|
||||
// 32 bytes = 43 characters when base64url encoded without padding
|
||||
bytes := make([]byte, 32)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate PKCE verifier: %w", err)
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
// generatePKCEChallenge generates a code challenge from a verifier using S256 method
|
||||
func generatePKCEChallenge(verifier string) string {
|
||||
hash := sha256.Sum256([]byte(verifier))
|
||||
return base64.RawURLEncoding.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func noPort(host string) string {
|
||||
return strings.Split(host, ":")[0]
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) getBaseURL(r *http.Request) string {
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
} else if p.options.TrustProxy && r.Header.Get("X-Forwarded-Proto") == "https" {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
host := r.Host
|
||||
if p.options.Hostname != "" {
|
||||
host = p.options.Hostname
|
||||
} else if p.options.TrustProxy {
|
||||
if xfHost := r.Header.Get("X-Forwarded-Host"); xfHost != "" {
|
||||
host = xfHost
|
||||
}
|
||||
}
|
||||
|
||||
return scheme + "://" + host
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) isSecure(r *http.Request) bool {
|
||||
_ = r
|
||||
return p.cookieSecure
|
||||
}
|
||||
|
||||
// InitiateOIDCFlow starts the OIDC authentication flow by redirecting to the provider
|
||||
func (p *OIDCProvider) InitiateOIDCFlow(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
|
||||
return p.initiateOIDCFlow(w, r)
|
||||
}
|
||||
|
||||
// HandleCallback processes the OIDC callback and returns the authenticated user token
|
||||
func (p *OIDCProvider) HandleCallback(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
|
||||
return p.handleCallback(w, r)
|
||||
}
|
||||
|
||||
func mergeOIDCClaims(primary, secondary map[string]interface{}) map[string]interface{} {
|
||||
// primary has precedence; fill missing/empty values from secondary.
|
||||
merged := make(map[string]interface{}, len(primary)+len(secondary))
|
||||
for k, v := range primary {
|
||||
merged[k] = v
|
||||
}
|
||||
for k, v := range secondary {
|
||||
if existing, ok := merged[k]; !ok || isEmptyClaim(existing) {
|
||||
merged[k] = v
|
||||
}
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func isEmptyClaim(v interface{}) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return val == ""
|
||||
case []interface{}:
|
||||
return len(val) == 0
|
||||
case []string:
|
||||
return len(val) == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,11 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
r.Post("/users/register", chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
|
||||
r.Post("/users/login", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
|
||||
|
||||
if a.conf.OIDC.Enabled {
|
||||
r.Get("/users/login/oidc", chain.ToHandlerFunc(v1Ctrl.HandleOIDCLogin()))
|
||||
r.Get("/users/login/oidc/callback", chain.ToHandlerFunc(v1Ctrl.HandleOIDCCallback()))
|
||||
}
|
||||
|
||||
userMW := []errchain.Middleware{
|
||||
a.mwAuthToken,
|
||||
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
|
||||
@@ -84,8 +89,6 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
r.Get("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
|
||||
r.Put("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
|
||||
r.Delete("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
|
||||
r.Get("/users/self/settings", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfSettingsGet(), userMW...))
|
||||
r.Put("/users/self/settings", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfSettingsUpdate(), userMW...))
|
||||
r.Post("/users/logout", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
|
||||
r.Get("/users/refresh", chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
|
||||
r.Put("/users/self/change-password", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
|
||||
@@ -105,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...))
|
||||
@@ -142,6 +146,14 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
|
||||
r.Get("/assets/{id}", chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
|
||||
|
||||
// Item Templates
|
||||
r.Get("/templates", chain.ToHandlerFunc(v1Ctrl.HandleItemTemplatesGetAll(), userMW...))
|
||||
r.Post("/templates", chain.ToHandlerFunc(v1Ctrl.HandleItemTemplatesCreate(), userMW...))
|
||||
r.Get("/templates/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemTemplatesGet(), userMW...))
|
||||
r.Put("/templates/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemTemplatesUpdate(), userMW...))
|
||||
r.Delete("/templates/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemTemplatesDelete(), userMW...))
|
||||
r.Post("/templates/{id}/create-item", chain.ToHandlerFunc(v1Ctrl.HandleItemTemplatesCreateItem(), userMW...))
|
||||
|
||||
// Maintenance
|
||||
r.Get("/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceGetAll(), userMW...))
|
||||
r.Put("/maintenance/{id}", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
|
||||
|
||||
@@ -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)
|
||||
|
||||
5453
backend/app/api/static/docs/openapi-3.json
Normal file
3492
backend/app/api/static/docs/openapi-3.yaml
Normal file
@@ -34,6 +34,8 @@ definitions:
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
decimals:
|
||||
type: integer
|
||||
local:
|
||||
type: string
|
||||
name:
|
||||
@@ -177,6 +179,11 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/ent.GroupInvitationToken'
|
||||
type: array
|
||||
item_templates:
|
||||
description: ItemTemplates holds the value of the item_templates edge.
|
||||
items:
|
||||
$ref: '#/definitions/ent.ItemTemplate'
|
||||
type: array
|
||||
items:
|
||||
description: Items holds the value of the items edge.
|
||||
items:
|
||||
@@ -412,6 +419,92 @@ definitions:
|
||||
- $ref: '#/definitions/ent.Item'
|
||||
description: Item holds the value of the item edge.
|
||||
type: object
|
||||
ent.ItemTemplate:
|
||||
properties:
|
||||
created_at:
|
||||
description: CreatedAt holds the value of the "created_at" field.
|
||||
type: string
|
||||
default_description:
|
||||
description: Default description for items created from this template
|
||||
type: string
|
||||
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.
|
||||
type: boolean
|
||||
default_manufacturer:
|
||||
description: DefaultManufacturer holds the value of the "default_manufacturer"
|
||||
field.
|
||||
type: string
|
||||
default_model_number:
|
||||
description: Default model number for items created from this template
|
||||
type: string
|
||||
default_name:
|
||||
description: Default name template for items (can use placeholders)
|
||||
type: string
|
||||
default_quantity:
|
||||
description: DefaultQuantity holds the value of the "default_quantity" field.
|
||||
type: integer
|
||||
default_warranty_details:
|
||||
description: DefaultWarrantyDetails holds the value of the "default_warranty_details"
|
||||
field.
|
||||
type: string
|
||||
description:
|
||||
description: Description holds the value of the "description" field.
|
||||
type: string
|
||||
edges:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.ItemTemplateEdges'
|
||||
description: |-
|
||||
Edges holds the relations/edges for other nodes in the graph.
|
||||
The values are being populated by the ItemTemplateQuery when eager-loading is set.
|
||||
id:
|
||||
description: ID of the ent.
|
||||
type: string
|
||||
include_purchase_fields:
|
||||
description: Whether to include purchase fields in items created from this
|
||||
template
|
||||
type: boolean
|
||||
include_sold_fields:
|
||||
description: Whether to include sold fields in items created from this template
|
||||
type: boolean
|
||||
include_warranty_fields:
|
||||
description: Whether to include warranty fields in items created from this
|
||||
template
|
||||
type: boolean
|
||||
name:
|
||||
description: Name holds the value of the "name" field.
|
||||
type: string
|
||||
notes:
|
||||
description: Notes holds the value of the "notes" field.
|
||||
type: string
|
||||
updated_at:
|
||||
description: UpdatedAt holds the value of the "updated_at" field.
|
||||
type: string
|
||||
type: object
|
||||
ent.ItemTemplateEdges:
|
||||
properties:
|
||||
fields:
|
||||
description: Fields holds the value of the fields edge.
|
||||
items:
|
||||
$ref: '#/definitions/ent.TemplateField'
|
||||
type: array
|
||||
group:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.Group'
|
||||
description: Group holds the value of the group edge.
|
||||
location:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.Location'
|
||||
description: Location holds the value of the location edge.
|
||||
type: object
|
||||
ent.Label:
|
||||
properties:
|
||||
color:
|
||||
@@ -580,6 +673,44 @@ definitions:
|
||||
- $ref: '#/definitions/ent.User'
|
||||
description: User holds the value of the user edge.
|
||||
type: object
|
||||
ent.TemplateField:
|
||||
properties:
|
||||
created_at:
|
||||
description: CreatedAt holds the value of the "created_at" field.
|
||||
type: string
|
||||
description:
|
||||
description: Description holds the value of the "description" field.
|
||||
type: string
|
||||
edges:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.TemplateFieldEdges'
|
||||
description: |-
|
||||
Edges holds the relations/edges for other nodes in the graph.
|
||||
The values are being populated by the TemplateFieldQuery 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
|
||||
text_value:
|
||||
description: TextValue holds the value of the "text_value" field.
|
||||
type: string
|
||||
type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/templatefield.Type'
|
||||
description: Type holds the value of the "type" field.
|
||||
updated_at:
|
||||
description: UpdatedAt holds the value of the "updated_at" field.
|
||||
type: string
|
||||
type: object
|
||||
ent.TemplateFieldEdges:
|
||||
properties:
|
||||
item_template:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.ItemTemplate'
|
||||
description: ItemTemplate holds the value of the item_template edge.
|
||||
type: object
|
||||
ent.User:
|
||||
properties:
|
||||
activated_on:
|
||||
@@ -606,14 +737,16 @@ definitions:
|
||||
name:
|
||||
description: Name holds the value of the "name" field.
|
||||
type: string
|
||||
oidc_issuer:
|
||||
description: OidcIssuer holds the value of the "oidc_issuer" field.
|
||||
type: string
|
||||
oidc_subject:
|
||||
description: OidcSubject holds the value of the "oidc_subject" field.
|
||||
type: string
|
||||
role:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.Role'
|
||||
description: Role holds the value of the "role" field.
|
||||
settings:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.UserSettings'
|
||||
description: Settings holds the value of the "settings" field.
|
||||
superuser:
|
||||
description: Superuser holds the value of the "superuser" field.
|
||||
type: boolean
|
||||
@@ -877,6 +1010,16 @@ definitions:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
labelIds:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-nullable: true
|
||||
x-omitempty: true
|
||||
locationId:
|
||||
type: string
|
||||
x-nullable: true
|
||||
x-omitempty: true
|
||||
quantity:
|
||||
type: integer
|
||||
x-nullable: true
|
||||
@@ -936,6 +1079,201 @@ definitions:
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
repo.ItemTemplateCreate:
|
||||
properties:
|
||||
defaultDescription:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultInsured:
|
||||
type: boolean
|
||||
defaultLabelIds:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-nullable: true
|
||||
defaultLifetimeWarranty:
|
||||
type: boolean
|
||||
defaultLocationId:
|
||||
description: Default location and labels
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultManufacturer:
|
||||
maxLength: 255
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultModelNumber:
|
||||
maxLength: 255
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultName:
|
||||
maxLength: 255
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultQuantity:
|
||||
description: Default values for items
|
||||
type: integer
|
||||
x-nullable: true
|
||||
defaultWarrantyDetails:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
x-nullable: true
|
||||
description:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
fields:
|
||||
description: Custom fields
|
||||
items:
|
||||
$ref: '#/definitions/repo.TemplateField'
|
||||
type: array
|
||||
includePurchaseFields:
|
||||
type: boolean
|
||||
includeSoldFields:
|
||||
type: boolean
|
||||
includeWarrantyFields:
|
||||
description: Metadata flags
|
||||
type: boolean
|
||||
name:
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
type: string
|
||||
notes:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
repo.ItemTemplateOut:
|
||||
properties:
|
||||
createdAt:
|
||||
type: string
|
||||
defaultDescription:
|
||||
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
|
||||
defaultManufacturer:
|
||||
type: string
|
||||
defaultModelNumber:
|
||||
type: string
|
||||
defaultName:
|
||||
type: string
|
||||
defaultQuantity:
|
||||
description: Default values for items
|
||||
type: integer
|
||||
defaultWarrantyDetails:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
fields:
|
||||
description: Custom fields
|
||||
items:
|
||||
$ref: '#/definitions/repo.TemplateField'
|
||||
type: array
|
||||
id:
|
||||
type: string
|
||||
includePurchaseFields:
|
||||
type: boolean
|
||||
includeSoldFields:
|
||||
type: boolean
|
||||
includeWarrantyFields:
|
||||
description: Metadata flags
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
notes:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
repo.ItemTemplateSummary:
|
||||
properties:
|
||||
createdAt:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
repo.ItemTemplateUpdate:
|
||||
properties:
|
||||
defaultDescription:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultInsured:
|
||||
type: boolean
|
||||
defaultLabelIds:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-nullable: true
|
||||
defaultLifetimeWarranty:
|
||||
type: boolean
|
||||
defaultLocationId:
|
||||
description: Default location and labels
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultManufacturer:
|
||||
maxLength: 255
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultModelNumber:
|
||||
maxLength: 255
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultName:
|
||||
maxLength: 255
|
||||
type: string
|
||||
x-nullable: true
|
||||
defaultQuantity:
|
||||
description: Default values for items
|
||||
type: integer
|
||||
x-nullable: true
|
||||
defaultWarrantyDetails:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
x-nullable: true
|
||||
description:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
fields:
|
||||
description: Custom fields
|
||||
items:
|
||||
$ref: '#/definitions/repo.TemplateField'
|
||||
type: array
|
||||
id:
|
||||
type: string
|
||||
includePurchaseFields:
|
||||
type: boolean
|
||||
includeSoldFields:
|
||||
type: boolean
|
||||
includeWarrantyFields:
|
||||
description: Metadata flags
|
||||
type: boolean
|
||||
name:
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
type: string
|
||||
notes:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
repo.ItemType:
|
||||
enum:
|
||||
- location
|
||||
@@ -1273,6 +1611,31 @@ definitions:
|
||||
total:
|
||||
type: integer
|
||||
type: object
|
||||
repo.TemplateField:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
textValue:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
repo.TemplateLabelSummary:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
repo.TemplateLocationSummary:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
repo.TotalsByOrganizer:
|
||||
properties:
|
||||
id:
|
||||
@@ -1311,6 +1674,10 @@ definitions:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
oidcIssuer:
|
||||
type: string
|
||||
oidcSubject:
|
||||
type: string
|
||||
type: object
|
||||
repo.UserUpdate:
|
||||
properties:
|
||||
@@ -1343,44 +1710,6 @@ definitions:
|
||||
value:
|
||||
type: number
|
||||
type: object
|
||||
schema.DuplicateSettings:
|
||||
properties:
|
||||
copyAttachments:
|
||||
type: boolean
|
||||
copyCustomFields:
|
||||
type: boolean
|
||||
copyMaintenance:
|
||||
type: boolean
|
||||
copyPrefixOverride:
|
||||
type: string
|
||||
type: object
|
||||
schema.UserSettings:
|
||||
properties:
|
||||
displayLegacyHeader:
|
||||
type: boolean
|
||||
duplicateSettings:
|
||||
$ref: '#/definitions/schema.DuplicateSettings'
|
||||
editorAdvancedView:
|
||||
type: boolean
|
||||
itemDisplayView:
|
||||
type: string
|
||||
itemsPerPage:
|
||||
type: integer
|
||||
itemsPerTablePage:
|
||||
type: integer
|
||||
language:
|
||||
type: string
|
||||
locale:
|
||||
type: string
|
||||
overrideFormatLocale:
|
||||
type: string
|
||||
showDetails:
|
||||
type: boolean
|
||||
showEmpty:
|
||||
type: boolean
|
||||
theme:
|
||||
type: string
|
||||
type: object
|
||||
services.Latest:
|
||||
properties:
|
||||
date:
|
||||
@@ -1399,6 +1728,12 @@ definitions:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
templatefield.Type:
|
||||
enum:
|
||||
- text
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- TypeText
|
||||
user.Role:
|
||||
enum:
|
||||
- user
|
||||
@@ -1425,6 +1760,8 @@ definitions:
|
||||
$ref: '#/definitions/services.Latest'
|
||||
message:
|
||||
type: string
|
||||
oidc:
|
||||
$ref: '#/definitions/v1.OIDCStatus'
|
||||
title:
|
||||
type: string
|
||||
versions:
|
||||
@@ -1478,6 +1815,27 @@ definitions:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
v1.ItemTemplateCreateItemRequest:
|
||||
properties:
|
||||
description:
|
||||
maxLength: 1000
|
||||
type: string
|
||||
labelIds:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
locationId:
|
||||
type: string
|
||||
name:
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
type: string
|
||||
quantity:
|
||||
type: integer
|
||||
required:
|
||||
- locationId
|
||||
- name
|
||||
type: object
|
||||
v1.LoginForm:
|
||||
properties:
|
||||
password:
|
||||
@@ -1489,6 +1847,17 @@ definitions:
|
||||
example: admin@admin.com
|
||||
type: string
|
||||
type: object
|
||||
v1.OIDCStatus:
|
||||
properties:
|
||||
allowLocal:
|
||||
type: boolean
|
||||
autoRedirect:
|
||||
type: boolean
|
||||
buttonText:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
type: object
|
||||
v1.TokenResponse:
|
||||
properties:
|
||||
attachmentToken:
|
||||
@@ -1498,6 +1867,15 @@ definitions:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
v1.WipeInventoryOptions:
|
||||
properties:
|
||||
wipeLabels:
|
||||
type: boolean
|
||||
wipeLocations:
|
||||
type: boolean
|
||||
wipeMaintenance:
|
||||
type: boolean
|
||||
type: object
|
||||
v1.Wrapped:
|
||||
properties:
|
||||
item: {}
|
||||
@@ -1578,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
|
||||
@@ -2709,6 +3108,130 @@ paths:
|
||||
summary: Application Info
|
||||
tags:
|
||||
- Base
|
||||
/v1/templates:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.ItemTemplateSummary'
|
||||
type: array
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Item Templates
|
||||
tags:
|
||||
- Item Templates
|
||||
post:
|
||||
parameters:
|
||||
- description: Template Data
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/repo.ItemTemplateCreate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/repo.ItemTemplateOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create Item Template
|
||||
tags:
|
||||
- Item Templates
|
||||
/v1/templates/{id}:
|
||||
delete:
|
||||
parameters:
|
||||
- description: Template ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Delete Item Template
|
||||
tags:
|
||||
- Item Templates
|
||||
get:
|
||||
parameters:
|
||||
- description: Template ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/repo.ItemTemplateOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get Item Template
|
||||
tags:
|
||||
- Item Templates
|
||||
put:
|
||||
parameters:
|
||||
- description: Template ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Template Data
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/repo.ItemTemplateUpdate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/repo.ItemTemplateOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Update Item Template
|
||||
tags:
|
||||
- Item Templates
|
||||
/v1/templates/{id}/create-item:
|
||||
post:
|
||||
parameters:
|
||||
- description: Template ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Item Data
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.ItemTemplateCreateItemRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/repo.ItemOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create Item from Template
|
||||
tags:
|
||||
- Item Templates
|
||||
/v1/users/change-password:
|
||||
put:
|
||||
parameters:
|
||||
@@ -2752,6 +3275,35 @@ paths:
|
||||
summary: User Login
|
||||
tags:
|
||||
- Authentication
|
||||
/v1/users/login/oidc:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"302":
|
||||
description: Found
|
||||
summary: OIDC Login Initiation
|
||||
tags:
|
||||
- Authentication
|
||||
/v1/users/login/oidc/callback:
|
||||
get:
|
||||
parameters:
|
||||
- description: Authorization code
|
||||
in: query
|
||||
name: code
|
||||
required: true
|
||||
type: string
|
||||
- description: State parameter
|
||||
in: query
|
||||
name: state
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"302":
|
||||
description: Found
|
||||
summary: OIDC Callback Handler
|
||||
tags:
|
||||
- Authentication
|
||||
/v1/users/logout:
|
||||
post:
|
||||
responses:
|
||||
@@ -2789,6 +3341,10 @@ paths:
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"403":
|
||||
description: Local login is not enabled
|
||||
schema:
|
||||
type: string
|
||||
summary: Register New User
|
||||
tags:
|
||||
- User
|
||||
@@ -2847,53 +3403,6 @@ paths:
|
||||
summary: Update Account
|
||||
tags:
|
||||
- User
|
||||
/v1/users/self/settings:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/v1.Wrapped'
|
||||
- properties:
|
||||
item:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type: object
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get user settings
|
||||
tags:
|
||||
- User
|
||||
put:
|
||||
parameters:
|
||||
- description: Settings Data
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/v1.Wrapped'
|
||||
- properties:
|
||||
item:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type: object
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get user settings
|
||||
tags:
|
||||
- User
|
||||
schemes:
|
||||
- https
|
||||
- http
|
||||
|
||||
197
backend/go.mod
@@ -6,15 +6,16 @@ toolchain go1.24.3
|
||||
|
||||
require (
|
||||
entgo.io/ent v0.14.5
|
||||
github.com/ardanlabs/conf/v3 v3.8.0
|
||||
github.com/ardanlabs/conf/v3 v3.10.0
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/evanoberholster/imagemeta v0.3.1
|
||||
github.com/gen2brain/avif v0.4.4
|
||||
github.com/gen2brain/heic v0.4.5
|
||||
github.com/gen2brain/heic v0.4.7
|
||||
github.com/gen2brain/jpegxl v0.4.5
|
||||
github.com/gen2brain/webp v0.5.5
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-playground/validator/v10 v10.27.0
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-playground/validator/v10 v10.30.1
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -22,40 +23,42 @@ require (
|
||||
github.com/hay-kot/httpkit v0.0.11
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/olahol/melody v1.3.0
|
||||
github.com/olahol/melody v1.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pressly/goose/v3 v3.24.3
|
||||
github.com/pressly/goose/v3 v3.26.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.7
|
||||
github.com/shirou/gopsutil/v4 v4.25.11
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2
|
||||
github.com/swaggo/swag v1.16.6
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.5
|
||||
github.com/yeqown/go-qrcode/writer/standard v1.3.0
|
||||
github.com/zeebo/blake3 v0.2.4
|
||||
go.balki.me/anyhttp v0.5.2
|
||||
gocloud.dev v0.43.0
|
||||
gocloud.dev/pubsub/kafkapubsub v0.43.0
|
||||
gocloud.dev/pubsub/natspubsub v0.43.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.43.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/image v0.30.0
|
||||
golang.org/x/text v0.28.0
|
||||
modernc.org/sqlite v1.38.2
|
||||
gocloud.dev v0.44.0
|
||||
gocloud.dev/pubsub/kafkapubsub v0.44.0
|
||||
gocloud.dev/pubsub/natspubsub v0.44.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.44.0
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/image v0.34.0
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
golang.org/x/text v0.32.0
|
||||
modernc.org/sqlite v1.41.0
|
||||
)
|
||||
|
||||
require (
|
||||
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go v0.121.4 // indirect
|
||||
cloud.google.com/go/auth v0.16.4 // indirect
|
||||
cloud.google.com/go v0.121.6 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/pubsub v1.49.0 // indirect
|
||||
cloud.google.com/go/storage v1.55.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
cloud.google.com/go/pubsub v1.50.1 // indirect
|
||||
cloud.google.com/go/pubsub/v2 v2.2.1 // indirect
|
||||
cloud.google.com/go/storage v1.56.0 // indirect
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect
|
||||
@@ -66,71 +69,74 @@ require (
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
|
||||
github.com/IBM/sarama v1.45.2 // indirect
|
||||
github.com/IBM/sarama v1.46.3 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.55.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.89.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.34.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
|
||||
github.com/aws/smithy-go v1.22.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect
|
||||
github.com/aws/smithy-go v1.23.2 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/eapache/go-resiliency v1.7.0 // indirect
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
||||
github.com/go-openapi/spec v0.22.3 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/wire v0.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/google/wire v0.7.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
@@ -138,73 +144,66 @@ require (
|
||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/nats-io/nats.go v1.44.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.23 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/sv-tools/openapi v0.2.1 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||
github.com/swaggo/swag/v2 v2.0.0-rc4 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zclconf/go-cty v1.14.4 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/api v0.247.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
google.golang.org/api v0.258.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
google.golang.org/grpc v1.78.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.66.7 // indirect
|
||||
modernc.org/libc v1.67.2 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
472
backend/go.sum
@@ -2,28 +2,30 @@ ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 h1:E0wvcUXTkgyN4wy4LGtNzMNG
|
||||
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs=
|
||||
cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s=
|
||||
cloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8=
|
||||
cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=
|
||||
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
|
||||
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
|
||||
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
|
||||
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
|
||||
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/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/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySPo=
|
||||
cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY=
|
||||
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
|
||||
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
|
||||
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/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.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
||||
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/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.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.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=
|
||||
@@ -61,78 +63,78 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJ
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
|
||||
github.com/IBM/sarama v1.45.2 h1:8m8LcMCu3REcwpa7fCP6v2fuPuzVwXDAM2DOv3CBrKw=
|
||||
github.com/IBM/sarama v1.45.2/go.mod h1:ppaoTcVdGv186/z6MEKsMm70A5fwJfRTpstI37kVn3Y=
|
||||
github.com/IBM/sarama v1.46.3 h1:njRsX6jNlnR+ClJ8XmkO+CM4unbrNr/2vB5KK6UA+IE=
|
||||
github.com/IBM/sarama v1.46.3/go.mod h1:GTUYiF9DMOZVe3FwyGT+dtSPceGFIgA+sPc5u6CBwko=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
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.8.0 h1:Mvv2wZJz8tIl705m5BU3ZRCP1V6TKY6qebA8i4sykrY=
|
||||
github.com/ardanlabs/conf/v3 v3.8.0/go.mod h1:XlL9P0quWP4m1weOVFmlezabinbZLI05niDof/+Ochk=
|
||||
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
|
||||
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
|
||||
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=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.17/go.mod h1:V8P7ILjp/Uef/aX8TjGk6OHZN6IKPM5YW6S78QnRD5c=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 h1:56HGpsgnmD+2/KpG0ikvvR8+3v3COCwaF4r+oWwOeNA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21/go.mod h1:3YELwedmQbw7cXNaII2Wywd+YY58AmLPwX4LzARgmmA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.3 h1:4GNV1lhyELGjMz5ILMRxDvxvOaeo3Ux9Z69S1EgVMMQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.3/go.mod h1:br7KA6edAAqDGUYJ+zVVPAyMrPhnN+zdt17yTUT6FPw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.89.2 h1:xgBWsgaeUESl8A8k80p6yBdexMWDVeiDmJ/pkjohJ7c=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.89.2/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.34.7 h1:OBuZE9Wt8h2imuRktu+WfjiTGrnYdCIJg8IX92aalHE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.34.7/go.mod h1:4WYoZAhHt+dWYpoOQUgkUKfuQbE6Gg/hW4oXE0pKS9U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.8 h1:80dpSqWMwx2dAm30Ib7J6ucz1ZHfiv5OCRwN/EnCOXQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.8/go.mod h1:IzNt/udsXlETCdvBOL0nmyMe2t9cGmXmZgsdoZGYYhI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
|
||||
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
|
||||
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ1lIMHMa+AHZSzz0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 h1:mLlUgHn02ue8whiR4BmxxGJLR2gwU6s6ZzJ5wDamBUs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk=
|
||||
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
|
||||
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=
|
||||
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=
|
||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
|
||||
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -149,12 +151,12 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=
|
||||
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
@@ -170,20 +172,20 @@ 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.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gabriel-vasile/mimetype v1.4.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.5 h1:Cq3hPu6wwlTJNv2t48ro3oWje54h82Q5pALeCBNgaSk=
|
||||
github.com/gen2brain/heic v0.4.5/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=
|
||||
github.com/gen2brain/jpegxl v0.4.5/go.mod h1:4kWYJ18xCEuO2vzocYdGpeqNJ990/Gjy3uLMg5TBN6I=
|
||||
github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg=
|
||||
github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -193,22 +195,41 @@ 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.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
|
||||
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/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.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.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=
|
||||
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
|
||||
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
|
||||
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
|
||||
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
|
||||
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
|
||||
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-playground/validator/v10 v10.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=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
@@ -226,7 +247,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -241,29 +261,25 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
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.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=
|
||||
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
|
||||
github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
||||
github.com/hay-kot/httpkit v0.0.11 h1:ZdB2uqsFBSDpfUoClGK5c5orjBjQkEVSXh7fZX5FKEk=
|
||||
@@ -282,18 +298,12 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -308,8 +318,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
@@ -317,8 +325,6 @@ 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=
|
||||
@@ -331,27 +337,24 @@ 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.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
|
||||
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
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=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olahol/melody v1.3.0 h1:n7UlKiQnxVrgxKoM0d7usZiN+Z0y2lVENtYLgKtXS6s=
|
||||
github.com/olahol/melody v1.3.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI=
|
||||
github.com/olahol/melody v1.4.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
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=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -361,10 +364,10 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM=
|
||||
github.com/pressly/goose/v3 v3.24.3/go.mod h1:v9zYL4xdViLHCUUJh/mhjnm6JrK7Eul8AS93IxiZM4E=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM=
|
||||
github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
||||
@@ -382,16 +385,12 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
||||
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
||||
github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
|
||||
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.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
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=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -401,26 +400,22 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/sv-tools/openapi v0.2.1 h1:ES1tMQMJFGibWndMagvdoo34T1Vllxr1Nlm5wz6b1aA=
|
||||
github.com/sv-tools/openapi v0.2.1/go.mod h1:k5VuZamTw1HuiS9p2Wl5YIDWzYnHG6/FgPOSFXLAhGg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
||||
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
|
||||
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/swaggo/swag/v2 v2.0.0-rc4 h1:SZ8cK68gcV6cslwrJMIOqPkJELRwq4gmjvk77MrvHvY=
|
||||
github.com/swaggo/swag/v2 v2.0.0-rc4/go.mod h1:Ow7Y8gF16BTCDn8YxZbyKn8FkMLRUHekv1kROJZpbvE=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
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=
|
||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.5 h1:HCOe2bSjkhZyYoyyNaXNzh4DJZll6inVJQQw+8228Zk=
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.5/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw=
|
||||
github.com/yeqown/go-qrcode/writer/standard v1.3.0 h1:chdyhEfRtUPgQtuPeaWVGQ/TQx4rE1PqeoW3U+53t34=
|
||||
@@ -438,62 +433,57 @@ github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.balki.me/anyhttp v0.5.2 h1:et4tCDXLeXpWfMNvRKG7ojfrnlr3du7cEaG966MLSpA=
|
||||
go.balki.me/anyhttp v0.5.2/go.mod h1:JhfekOIjgVODoVqUCficjpIgmB3wwlB7jhN0eN2EZ/s=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9plR/HNjgGBrfcOlweA=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=
|
||||
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.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
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.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
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.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.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=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
gocloud.dev v0.43.0 h1:aW3eq4RMyehbJ54PMsh4hsp7iX8cO/98ZRzJJOzN/5M=
|
||||
gocloud.dev v0.43.0/go.mod h1:eD8rkg7LhKUHrzkEdLTZ+Ty/vgPHPCd+yMQdfelQVu4=
|
||||
gocloud.dev/pubsub/kafkapubsub v0.43.0 h1:Kgwi0na69W3RgxEffEkdrMhox6A3Q0gajoJtjHGVr/s=
|
||||
gocloud.dev/pubsub/kafkapubsub v0.43.0/go.mod h1:uKI0CXuj7HJ/YnnOLQ3VkDnuUnkz+q/d+tRzmfhmOOU=
|
||||
gocloud.dev/pubsub/natspubsub v0.43.0 h1:k35tFoaorvD9Fa26zVEEzyXiMOEyXNHc0pBOmRYvQI0=
|
||||
gocloud.dev/pubsub/natspubsub v0.43.0/go.mod h1:xJn8TO8pGYieDn6AsRFsYfhQW8cnC+xGmG9APGNxkpQ=
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.43.0 h1:6nNZFSlJ1dk2GujL8PFltfLz3vC6IbrpjGS4FTduo1s=
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.43.0/go.mod h1:sEaueAGat+OASRoB3QDkghCtibKttgg7X6zsPTm1pl0=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
gocloud.dev v0.44.0 h1:iVyMAqFl2r6xUy7M4mfqwlN+21UpJoEtgHEcfiLMUXs=
|
||||
gocloud.dev v0.44.0/go.mod h1:ZmjROXGdC/eKZLF1N+RujDlFRx3D+4Av2thREKDMVxY=
|
||||
gocloud.dev/pubsub/kafkapubsub v0.44.0 h1:nQvzfnEN6lCh4j2p+1t0OLS4nmC2U/Ji5aWHVwgkifg=
|
||||
gocloud.dev/pubsub/kafkapubsub v0.44.0/go.mod h1:/gcNz6OG4HgcY+w2LXwwY4qaRMgtq+SXoPSQU2jOlcw=
|
||||
gocloud.dev/pubsub/natspubsub v0.44.0 h1:1Us76ckkdgtiE1p1rJZ+38b9TQP051bmjAiQlFQzYrM=
|
||||
gocloud.dev/pubsub/natspubsub v0.44.0/go.mod h1:PvVAGIhL14PWGwWIXX/zAK42ixr2/PKP4Q4yMiAUraQ=
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.44.0 h1:MpRIO6XJ/JTqrlUWt3CxwDe1LvaiXUVu4sS5cv4f/AM=
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.44.0/go.mod h1:BB9+qT3r6g4M5+4asiXaEeqw4QAOzsWusO5krYaqkdA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
|
||||
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
|
||||
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-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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.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=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -501,20 +491,14 @@ 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.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=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -527,74 +511,64 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.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=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
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=
|
||||
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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.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=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
|
||||
google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
|
||||
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs=
|
||||
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 h1:iOye66xuaAK0WnkPuhQPUFy8eJcmwUXqGGP3om6IxX8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79/go.mod h1:HKJDgKsFUnv5VAGeQjz8kxcgDP0HoE0iZNp0OdZNlhE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc=
|
||||
google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww=
|
||||
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-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-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.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.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=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
|
||||
modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/fileutil v1.3.15 h1:rJAXTP6ilMW/1+kzDiqmBlHLWszheUFXIyGQIAvjJpY=
|
||||
modernc.org/fileutil v1.3.15/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
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.66.7 h1:rjhZ8OSCybKWxS1CJr0hikpEi6Vg+944Ouyrd+bQsoY=
|
||||
modernc.org/libc v1.66.7/go.mod h1:ln6tbWX0NH+mzApEoDRvilBvAWFt1HX7AUA4VDdVDPM=
|
||||
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=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -603,8 +577,8 @@ 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.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
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=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -15,6 +16,24 @@ import (
|
||||
//go:embed currencies.json
|
||||
var defaults []byte
|
||||
|
||||
const (
|
||||
MinDecimals = 0
|
||||
MaxDecimals = 18
|
||||
)
|
||||
|
||||
// clampDecimals ensures the decimals value is within a safe range [0, 18]
|
||||
func clampDecimals(decimals int, code string) int {
|
||||
original := decimals
|
||||
if decimals < MinDecimals {
|
||||
decimals = MinDecimals
|
||||
log.Printf("WARNING: Currency %s had negative decimals (%d), normalized to %d", code, original, decimals)
|
||||
} else if decimals > MaxDecimals {
|
||||
decimals = MaxDecimals
|
||||
log.Printf("WARNING: Currency %s had excessive decimals (%d), normalized to %d", code, original, decimals)
|
||||
}
|
||||
return decimals
|
||||
}
|
||||
|
||||
type CollectorFunc func() ([]Currency, error)
|
||||
|
||||
func CollectJSON(reader io.Reader) CollectorFunc {
|
||||
@@ -25,6 +44,11 @@ func CollectJSON(reader io.Reader) CollectorFunc {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clamp decimals during collection to ensure early normalization
|
||||
for i := range currencies {
|
||||
currencies[i].Decimals = clampDecimals(currencies[i].Decimals, currencies[i].Code)
|
||||
}
|
||||
|
||||
return currencies, nil
|
||||
}
|
||||
}
|
||||
@@ -48,10 +72,11 @@ func CollectionCurrencies(collectors ...CollectorFunc) ([]Currency, error) {
|
||||
}
|
||||
|
||||
type Currency struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Local string `json:"local"`
|
||||
Symbol string `json:"symbol"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Local string `json:"local"`
|
||||
Symbol string `json:"symbol"`
|
||||
Decimals int `json:"decimals"`
|
||||
}
|
||||
|
||||
type CurrencyRegistry struct {
|
||||
@@ -62,7 +87,10 @@ type CurrencyRegistry struct {
|
||||
func NewCurrencyService(currencies []Currency) *CurrencyRegistry {
|
||||
registry := make(map[string]Currency, len(currencies))
|
||||
for i := range currencies {
|
||||
registry[currencies[i].Code] = currencies[i]
|
||||
// Clamp decimals to safe range before adding to registry
|
||||
currency := currencies[i]
|
||||
currency.Decimals = clampDecimals(currency.Decimals, currency.Code)
|
||||
registry[currency.Code] = currency
|
||||
}
|
||||
|
||||
return &CurrencyRegistry{
|
||||
|
||||
@@ -38,10 +38,11 @@ func bootstrap() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
password := fk.Str(10)
|
||||
tUser, err = tRepos.Users.Create(ctx, repo.UserCreate{
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: fk.Str(10),
|
||||
Password: &password,
|
||||
IsSuperuser: fk.Bool(),
|
||||
GroupID: tGroup.ID,
|
||||
})
|
||||
|
||||
@@ -50,6 +50,7 @@ func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename st
|
||||
_, err = svc.repo.Attachments.Create(ctx, itemID, repo.ItemCreateAttachment{Title: filename, Content: file}, attachmentType, primary)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to create attachment")
|
||||
return repo.ItemOut{}, err
|
||||
}
|
||||
|
||||
return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||
)
|
||||
|
||||
func TestItemService_AddAttachment(t *testing.T) {
|
||||
@@ -52,11 +53,61 @@ func TestItemService_AddAttachment(t *testing.T) {
|
||||
// Check that the file exists
|
||||
storedPath := afterAttachment.Attachments[0].Path
|
||||
|
||||
// {root}/{group}/{item}/{attachment}
|
||||
assert.Equal(t, path.Join("/", tGroup.ID.String(), "documents"), path.Dir(storedPath))
|
||||
// path should now be relative: {group}/{documents}
|
||||
assert.Equal(t, path.Join(tGroup.ID.String(), "documents"), path.Dir(storedPath))
|
||||
|
||||
// Check that the file contents are correct
|
||||
bts, err := os.ReadFile(path.Join(os.TempDir(), storedPath))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, contents, string(bts))
|
||||
}
|
||||
|
||||
func TestItemService_AddAttachment_InvalidStorage(t *testing.T) {
|
||||
// Create a service with an invalid storage path to simulate the issue
|
||||
svc := &ItemService{
|
||||
repo: tRepos,
|
||||
filepath: "/nonexistent/path/that/should/not/exist",
|
||||
}
|
||||
|
||||
// Create a temporary repo with invalid storage config
|
||||
invalidRepos := repo.New(tClient, tbus, config.Storage{
|
||||
PrefixPath: "/",
|
||||
ConnString: "file:///nonexistent/directory/that/does/not/exist",
|
||||
}, "mem://{{ .Topic }}", config.Thumbnail{
|
||||
Enabled: false,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
})
|
||||
|
||||
svc.repo = invalidRepos
|
||||
|
||||
loc, err := invalidRepos.Locations.Create(context.Background(), tGroup.ID, repo.LocationCreate{
|
||||
Description: "test",
|
||||
Name: "test-invalid",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, loc)
|
||||
|
||||
itmC := repo.ItemCreate{
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(10),
|
||||
LocationID: loc.ID,
|
||||
}
|
||||
|
||||
itm, err := invalidRepos.Items.Create(context.Background(), tGroup.ID, itmC)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, itm)
|
||||
t.Cleanup(func() {
|
||||
err := invalidRepos.Items.Delete(context.Background(), itm.ID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
contents := fk.Str(1000)
|
||||
reader := strings.NewReader(contents)
|
||||
|
||||
// Attempt to add attachment with invalid storage - should return an error
|
||||
_, err = svc.AttachmentAdd(tCtx, itm.ID, "testfile.txt", "attachment", false, reader)
|
||||
|
||||
// This should return an error now (after the fix)
|
||||
assert.Error(t, err, "AttachmentAdd should return an error when storage is invalid")
|
||||
}
|
||||
|
||||
@@ -3,21 +3,21 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authroles"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
"github.com/sysadminsmedia/homebox/backend/pkgs/hasher"
|
||||
)
|
||||
|
||||
var (
|
||||
oneWeek = time.Hour * 24 * 7
|
||||
ErrorInvalidLogin = errors.New("invalid username or password")
|
||||
ErrorInvalidToken = errors.New("invalid token")
|
||||
ErrorTokenIDMismatch = errors.New("token id mismatch")
|
||||
oneWeek = time.Hour * 24 * 7
|
||||
ErrorInvalidLogin = errors.New("invalid username or password")
|
||||
ErrorInvalidToken = errors.New("invalid token")
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
@@ -83,7 +83,7 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
|
||||
usrCreate := repo.UserCreate{
|
||||
Name: data.Name,
|
||||
Email: data.Email,
|
||||
Password: hashed,
|
||||
Password: &hashed,
|
||||
IsSuperuser: false,
|
||||
GroupID: group.ID,
|
||||
IsOwner: creatingGroup,
|
||||
@@ -191,6 +191,14 @@ func (svc *UserService) Login(ctx context.Context, username, password string, ex
|
||||
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||
}
|
||||
|
||||
// SECURITY: Deny login for users with null or empty password (OIDC users)
|
||||
if usr.PasswordHash == "" {
|
||||
log.Warn().Str("email", username).Msg("Login attempt blocked for user with null password (likely OIDC user)")
|
||||
// SECURITY: Perform hash to ensure response times are the same
|
||||
hasher.CheckPasswordHash("not-a-real-password", "not-a-real-password")
|
||||
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||
}
|
||||
|
||||
check, rehash := hasher.CheckPasswordHash(password, usr.PasswordHash)
|
||||
|
||||
if !check {
|
||||
@@ -211,6 +219,106 @@ func (svc *UserService) Login(ctx context.Context, username, password string, ex
|
||||
return svc.createSessionToken(ctx, usr.ID, extendedSession)
|
||||
}
|
||||
|
||||
// LoginOIDC creates a session token for a user authenticated via OIDC.
|
||||
// It now uses issuer + subject for identity association (OIDC spec compliance).
|
||||
// If the user doesn't exist, it will create one.
|
||||
func (svc *UserService) LoginOIDC(ctx context.Context, issuer, subject, email, name string) (UserAuthTokenDetail, error) {
|
||||
issuer = strings.TrimSpace(issuer)
|
||||
subject = strings.TrimSpace(subject)
|
||||
email = strings.ToLower(strings.TrimSpace(email))
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
if issuer == "" || subject == "" {
|
||||
log.Warn().Str("issuer", issuer).Str("subject", subject).Msg("OIDC login missing issuer or subject")
|
||||
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||
}
|
||||
|
||||
// Try to get existing user by OIDC identity
|
||||
usr, err := svc.repos.Users.GetOneOIDC(ctx, issuer, subject)
|
||||
if err != nil {
|
||||
if !ent.IsNotFound(err) {
|
||||
log.Err(err).Str("issuer", issuer).Str("subject", subject).Msg("failed to lookup user by OIDC identity")
|
||||
return UserAuthTokenDetail{}, err
|
||||
}
|
||||
// Not found: attempt migration path by email (legacy) if email provided
|
||||
if email != "" {
|
||||
legacyUsr, lerr := svc.repos.Users.GetOneEmail(ctx, email)
|
||||
if lerr == nil {
|
||||
log.Info().Str("email", email).Str("issuer", issuer).Str("subject", subject).Msg("migrating legacy email-based OIDC user to issuer+subject")
|
||||
// Update user with OIDC identity fields
|
||||
if uerr := svc.repos.Users.SetOIDCIdentity(ctx, legacyUsr.ID, issuer, subject); uerr == nil {
|
||||
usr = legacyUsr
|
||||
} else {
|
||||
log.Err(uerr).Str("email", email).Msg("failed to set OIDC identity on legacy user")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create user if still not resolved
|
||||
if usr.ID == uuid.Nil {
|
||||
log.Debug().Str("issuer", issuer).Str("subject", subject).Msg("OIDC user not found, creating new user")
|
||||
usr, err = svc.registerOIDCUser(ctx, issuer, subject, email, name)
|
||||
if err != nil {
|
||||
if ent.IsConstraintError(err) {
|
||||
if usr2, gerr := svc.repos.Users.GetOneOIDC(ctx, issuer, subject); gerr == nil {
|
||||
log.Info().Str("issuer", issuer).Str("subject", subject).Msg("OIDC user created concurrently; proceeding")
|
||||
usr = usr2
|
||||
} else {
|
||||
log.Err(gerr).Str("issuer", issuer).Str("subject", subject).Msg("failed to fetch user after constraint error")
|
||||
return UserAuthTokenDetail{}, gerr
|
||||
}
|
||||
} else {
|
||||
log.Err(err).Str("issuer", issuer).Str("subject", subject).Msg("failed to create OIDC user")
|
||||
return UserAuthTokenDetail{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return svc.createSessionToken(ctx, usr.ID, true)
|
||||
}
|
||||
|
||||
// registerOIDCUser creates a new user for OIDC authentication with issuer+subject identity.
|
||||
func (svc *UserService) registerOIDCUser(ctx context.Context, issuer, subject, email, name string) (repo.UserOut, error) {
|
||||
group, err := svc.repos.Groups.GroupCreate(ctx, "Home")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create group for OIDC user")
|
||||
return repo.UserOut{}, err
|
||||
}
|
||||
|
||||
usrCreate := repo.UserCreate{
|
||||
Name: name,
|
||||
Email: email,
|
||||
Password: nil,
|
||||
IsSuperuser: false,
|
||||
GroupID: group.ID,
|
||||
IsOwner: true,
|
||||
}
|
||||
|
||||
entUser, err := svc.repos.Users.CreateWithOIDC(ctx, usrCreate, issuer, subject)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create default label")
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Str("issuer", issuer).Str("subject", subject).Msg("creating default locations for OIDC user")
|
||||
for _, location := range defaultLocations() {
|
||||
_, err := svc.repos.Locations.Create(ctx, group.ID, location)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create default location")
|
||||
}
|
||||
}
|
||||
|
||||
return entUser, nil
|
||||
}
|
||||
|
||||
func (svc *UserService) Logout(ctx context.Context, token string) error {
|
||||
hash := hasher.HashToken(token)
|
||||
err := svc.repos.AuthTokens.DeleteToken(ctx, hash)
|
||||
@@ -261,11 +369,3 @@ func (svc *UserService) ChangePassword(ctx Context, current string, new string)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (svc *UserService) GetSettings(ctx context.Context, uid uuid.UUID) (schema.UserSettings, error) {
|
||||
return svc.repos.Users.GetSettings(ctx, uid)
|
||||
}
|
||||
|
||||
func (svc *UserService) SetSettings(ctx context.Context, uid uuid.UUID, settings schema.UserSettings) error {
|
||||
return svc.repos.Users.SetSettings(ctx, uid, settings)
|
||||
}
|
||||
|
||||
376
backend/internal/data/ent/client.go
generated
@@ -23,10 +23,12 @@ 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/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/templatefield"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
|
||||
)
|
||||
|
||||
@@ -49,6 +51,8 @@ type Client struct {
|
||||
Item *ItemClient
|
||||
// ItemField is the client for interacting with the ItemField builders.
|
||||
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.
|
||||
@@ -57,6 +61,8 @@ type Client struct {
|
||||
MaintenanceEntry *MaintenanceEntryClient
|
||||
// Notifier is the client for interacting with the Notifier builders.
|
||||
Notifier *NotifierClient
|
||||
// TemplateField is the client for interacting with the TemplateField builders.
|
||||
TemplateField *TemplateFieldClient
|
||||
// User is the client for interacting with the User builders.
|
||||
User *UserClient
|
||||
}
|
||||
@@ -77,10 +83,12 @@ func (c *Client) init() {
|
||||
c.GroupInvitationToken = NewGroupInvitationTokenClient(c.config)
|
||||
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.TemplateField = NewTemplateFieldClient(c.config)
|
||||
c.User = NewUserClient(c.config)
|
||||
}
|
||||
|
||||
@@ -181,10 +189,12 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
||||
GroupInvitationToken: NewGroupInvitationTokenClient(cfg),
|
||||
Item: NewItemClient(cfg),
|
||||
ItemField: NewItemFieldClient(cfg),
|
||||
ItemTemplate: NewItemTemplateClient(cfg),
|
||||
Label: NewLabelClient(cfg),
|
||||
Location: NewLocationClient(cfg),
|
||||
MaintenanceEntry: NewMaintenanceEntryClient(cfg),
|
||||
Notifier: NewNotifierClient(cfg),
|
||||
TemplateField: NewTemplateFieldClient(cfg),
|
||||
User: NewUserClient(cfg),
|
||||
}, nil
|
||||
}
|
||||
@@ -212,10 +222,12 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
||||
GroupInvitationToken: NewGroupInvitationTokenClient(cfg),
|
||||
Item: NewItemClient(cfg),
|
||||
ItemField: NewItemFieldClient(cfg),
|
||||
ItemTemplate: NewItemTemplateClient(cfg),
|
||||
Label: NewLabelClient(cfg),
|
||||
Location: NewLocationClient(cfg),
|
||||
MaintenanceEntry: NewMaintenanceEntryClient(cfg),
|
||||
Notifier: NewNotifierClient(cfg),
|
||||
TemplateField: NewTemplateFieldClient(cfg),
|
||||
User: NewUserClient(cfg),
|
||||
}, nil
|
||||
}
|
||||
@@ -247,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.Label, c.Location, c.MaintenanceEntry, c.Notifier,
|
||||
c.User,
|
||||
c.Item, c.ItemField, c.ItemTemplate, c.Label, c.Location, c.MaintenanceEntry,
|
||||
c.Notifier, c.TemplateField, c.User,
|
||||
} {
|
||||
n.Use(hooks...)
|
||||
}
|
||||
@@ -259,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.Label, c.Location, c.MaintenanceEntry, c.Notifier,
|
||||
c.User,
|
||||
c.Item, c.ItemField, c.ItemTemplate, c.Label, c.Location, c.MaintenanceEntry,
|
||||
c.Notifier, c.TemplateField, c.User,
|
||||
} {
|
||||
n.Intercept(interceptors...)
|
||||
}
|
||||
@@ -283,6 +295,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
return c.Item.mutate(ctx, m)
|
||||
case *ItemFieldMutation:
|
||||
return c.ItemField.mutate(ctx, m)
|
||||
case *ItemTemplateMutation:
|
||||
return c.ItemTemplate.mutate(ctx, m)
|
||||
case *LabelMutation:
|
||||
return c.Label.mutate(ctx, m)
|
||||
case *LocationMutation:
|
||||
@@ -291,6 +305,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
return c.MaintenanceEntry.mutate(ctx, m)
|
||||
case *NotifierMutation:
|
||||
return c.Notifier.mutate(ctx, m)
|
||||
case *TemplateFieldMutation:
|
||||
return c.TemplateField.mutate(ctx, m)
|
||||
case *UserMutation:
|
||||
return c.User.mutate(ctx, m)
|
||||
default:
|
||||
@@ -981,6 +997,22 @@ func (c *GroupClient) QueryNotifiers(_m *Group) *NotifierQuery {
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryItemTemplates queries the item_templates edge of a Group.
|
||||
func (c *GroupClient) QueryItemTemplates(_m *Group) *ItemTemplateQuery {
|
||||
query := (&ItemTemplateClient{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(itemtemplate.Table, itemtemplate.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, group.ItemTemplatesTable, group.ItemTemplatesColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *GroupClient) Hooks() []Hook {
|
||||
return c.hooks.Group
|
||||
@@ -1565,6 +1597,187 @@ func (c *ItemFieldClient) mutate(ctx context.Context, m *ItemFieldMutation) (Val
|
||||
}
|
||||
}
|
||||
|
||||
// ItemTemplateClient is a client for the ItemTemplate schema.
|
||||
type ItemTemplateClient struct {
|
||||
config
|
||||
}
|
||||
|
||||
// NewItemTemplateClient returns a client for the ItemTemplate from the given config.
|
||||
func NewItemTemplateClient(c config) *ItemTemplateClient {
|
||||
return &ItemTemplateClient{config: c}
|
||||
}
|
||||
|
||||
// Use adds a list of mutation hooks to the hooks stack.
|
||||
// A call to `Use(f, g, h)` equals to `itemtemplate.Hooks(f(g(h())))`.
|
||||
func (c *ItemTemplateClient) Use(hooks ...Hook) {
|
||||
c.hooks.ItemTemplate = append(c.hooks.ItemTemplate, hooks...)
|
||||
}
|
||||
|
||||
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||
// A call to `Intercept(f, g, h)` equals to `itemtemplate.Intercept(f(g(h())))`.
|
||||
func (c *ItemTemplateClient) Intercept(interceptors ...Interceptor) {
|
||||
c.inters.ItemTemplate = append(c.inters.ItemTemplate, interceptors...)
|
||||
}
|
||||
|
||||
// Create returns a builder for creating a ItemTemplate entity.
|
||||
func (c *ItemTemplateClient) Create() *ItemTemplateCreate {
|
||||
mutation := newItemTemplateMutation(c.config, OpCreate)
|
||||
return &ItemTemplateCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// CreateBulk returns a builder for creating a bulk of ItemTemplate entities.
|
||||
func (c *ItemTemplateClient) CreateBulk(builders ...*ItemTemplateCreate) *ItemTemplateCreateBulk {
|
||||
return &ItemTemplateCreateBulk{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 *ItemTemplateClient) MapCreateBulk(slice any, setFunc func(*ItemTemplateCreate, int)) *ItemTemplateCreateBulk {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
return &ItemTemplateCreateBulk{err: fmt.Errorf("calling to ItemTemplateClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||
}
|
||||
builders := make([]*ItemTemplateCreate, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
builders[i] = c.Create()
|
||||
setFunc(builders[i], i)
|
||||
}
|
||||
return &ItemTemplateCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// Update returns an update builder for ItemTemplate.
|
||||
func (c *ItemTemplateClient) Update() *ItemTemplateUpdate {
|
||||
mutation := newItemTemplateMutation(c.config, OpUpdate)
|
||||
return &ItemTemplateUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOne returns an update builder for the given entity.
|
||||
func (c *ItemTemplateClient) UpdateOne(_m *ItemTemplate) *ItemTemplateUpdateOne {
|
||||
mutation := newItemTemplateMutation(c.config, OpUpdateOne, withItemTemplate(_m))
|
||||
return &ItemTemplateUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOneID returns an update builder for the given id.
|
||||
func (c *ItemTemplateClient) UpdateOneID(id uuid.UUID) *ItemTemplateUpdateOne {
|
||||
mutation := newItemTemplateMutation(c.config, OpUpdateOne, withItemTemplateID(id))
|
||||
return &ItemTemplateUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// Delete returns a delete builder for ItemTemplate.
|
||||
func (c *ItemTemplateClient) Delete() *ItemTemplateDelete {
|
||||
mutation := newItemTemplateMutation(c.config, OpDelete)
|
||||
return &ItemTemplateDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// DeleteOne returns a builder for deleting the given entity.
|
||||
func (c *ItemTemplateClient) DeleteOne(_m *ItemTemplate) *ItemTemplateDeleteOne {
|
||||
return c.DeleteOneID(_m.ID)
|
||||
}
|
||||
|
||||
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||
func (c *ItemTemplateClient) DeleteOneID(id uuid.UUID) *ItemTemplateDeleteOne {
|
||||
builder := c.Delete().Where(itemtemplate.ID(id))
|
||||
builder.mutation.id = &id
|
||||
builder.mutation.op = OpDeleteOne
|
||||
return &ItemTemplateDeleteOne{builder}
|
||||
}
|
||||
|
||||
// Query returns a query builder for ItemTemplate.
|
||||
func (c *ItemTemplateClient) Query() *ItemTemplateQuery {
|
||||
return &ItemTemplateQuery{
|
||||
config: c.config,
|
||||
ctx: &QueryContext{Type: TypeItemTemplate},
|
||||
inters: c.Interceptors(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a ItemTemplate entity by its id.
|
||||
func (c *ItemTemplateClient) Get(ctx context.Context, id uuid.UUID) (*ItemTemplate, error) {
|
||||
return c.Query().Where(itemtemplate.ID(id)).Only(ctx)
|
||||
}
|
||||
|
||||
// GetX is like Get, but panics if an error occurs.
|
||||
func (c *ItemTemplateClient) GetX(ctx context.Context, id uuid.UUID) *ItemTemplate {
|
||||
obj, err := c.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// QueryGroup queries the group edge of a ItemTemplate.
|
||||
func (c *ItemTemplateClient) QueryGroup(_m *ItemTemplate) *GroupQuery {
|
||||
query := (&GroupClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(itemtemplate.Table, itemtemplate.FieldID, id),
|
||||
sqlgraph.To(group.Table, group.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, itemtemplate.GroupTable, itemtemplate.GroupColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryFields queries the fields edge of a ItemTemplate.
|
||||
func (c *ItemTemplateClient) QueryFields(_m *ItemTemplate) *TemplateFieldQuery {
|
||||
query := (&TemplateFieldClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(itemtemplate.Table, itemtemplate.FieldID, id),
|
||||
sqlgraph.To(templatefield.Table, templatefield.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, itemtemplate.FieldsTable, itemtemplate.FieldsColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryLocation queries the location edge of a ItemTemplate.
|
||||
func (c *ItemTemplateClient) QueryLocation(_m *ItemTemplate) *LocationQuery {
|
||||
query := (&LocationClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(itemtemplate.Table, itemtemplate.FieldID, id),
|
||||
sqlgraph.To(location.Table, location.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, false, itemtemplate.LocationTable, itemtemplate.LocationColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *ItemTemplateClient) Hooks() []Hook {
|
||||
return c.hooks.ItemTemplate
|
||||
}
|
||||
|
||||
// Interceptors returns the client interceptors.
|
||||
func (c *ItemTemplateClient) Interceptors() []Interceptor {
|
||||
return c.inters.ItemTemplate
|
||||
}
|
||||
|
||||
func (c *ItemTemplateClient) mutate(ctx context.Context, m *ItemTemplateMutation) (Value, error) {
|
||||
switch m.Op() {
|
||||
case OpCreate:
|
||||
return (&ItemTemplateCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdate:
|
||||
return (&ItemTemplateUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdateOne:
|
||||
return (&ItemTemplateUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpDelete, OpDeleteOne:
|
||||
return (&ItemTemplateDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("ent: unknown ItemTemplate mutation op: %q", m.Op())
|
||||
}
|
||||
}
|
||||
|
||||
// LabelClient is a client for the Label schema.
|
||||
type LabelClient struct {
|
||||
config
|
||||
@@ -2241,6 +2454,155 @@ func (c *NotifierClient) mutate(ctx context.Context, m *NotifierMutation) (Value
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateFieldClient is a client for the TemplateField schema.
|
||||
type TemplateFieldClient struct {
|
||||
config
|
||||
}
|
||||
|
||||
// NewTemplateFieldClient returns a client for the TemplateField from the given config.
|
||||
func NewTemplateFieldClient(c config) *TemplateFieldClient {
|
||||
return &TemplateFieldClient{config: c}
|
||||
}
|
||||
|
||||
// Use adds a list of mutation hooks to the hooks stack.
|
||||
// A call to `Use(f, g, h)` equals to `templatefield.Hooks(f(g(h())))`.
|
||||
func (c *TemplateFieldClient) Use(hooks ...Hook) {
|
||||
c.hooks.TemplateField = append(c.hooks.TemplateField, hooks...)
|
||||
}
|
||||
|
||||
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||
// A call to `Intercept(f, g, h)` equals to `templatefield.Intercept(f(g(h())))`.
|
||||
func (c *TemplateFieldClient) Intercept(interceptors ...Interceptor) {
|
||||
c.inters.TemplateField = append(c.inters.TemplateField, interceptors...)
|
||||
}
|
||||
|
||||
// Create returns a builder for creating a TemplateField entity.
|
||||
func (c *TemplateFieldClient) Create() *TemplateFieldCreate {
|
||||
mutation := newTemplateFieldMutation(c.config, OpCreate)
|
||||
return &TemplateFieldCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// CreateBulk returns a builder for creating a bulk of TemplateField entities.
|
||||
func (c *TemplateFieldClient) CreateBulk(builders ...*TemplateFieldCreate) *TemplateFieldCreateBulk {
|
||||
return &TemplateFieldCreateBulk{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 *TemplateFieldClient) MapCreateBulk(slice any, setFunc func(*TemplateFieldCreate, int)) *TemplateFieldCreateBulk {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
return &TemplateFieldCreateBulk{err: fmt.Errorf("calling to TemplateFieldClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||
}
|
||||
builders := make([]*TemplateFieldCreate, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
builders[i] = c.Create()
|
||||
setFunc(builders[i], i)
|
||||
}
|
||||
return &TemplateFieldCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// Update returns an update builder for TemplateField.
|
||||
func (c *TemplateFieldClient) Update() *TemplateFieldUpdate {
|
||||
mutation := newTemplateFieldMutation(c.config, OpUpdate)
|
||||
return &TemplateFieldUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOne returns an update builder for the given entity.
|
||||
func (c *TemplateFieldClient) UpdateOne(_m *TemplateField) *TemplateFieldUpdateOne {
|
||||
mutation := newTemplateFieldMutation(c.config, OpUpdateOne, withTemplateField(_m))
|
||||
return &TemplateFieldUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOneID returns an update builder for the given id.
|
||||
func (c *TemplateFieldClient) UpdateOneID(id uuid.UUID) *TemplateFieldUpdateOne {
|
||||
mutation := newTemplateFieldMutation(c.config, OpUpdateOne, withTemplateFieldID(id))
|
||||
return &TemplateFieldUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// Delete returns a delete builder for TemplateField.
|
||||
func (c *TemplateFieldClient) Delete() *TemplateFieldDelete {
|
||||
mutation := newTemplateFieldMutation(c.config, OpDelete)
|
||||
return &TemplateFieldDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// DeleteOne returns a builder for deleting the given entity.
|
||||
func (c *TemplateFieldClient) DeleteOne(_m *TemplateField) *TemplateFieldDeleteOne {
|
||||
return c.DeleteOneID(_m.ID)
|
||||
}
|
||||
|
||||
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||
func (c *TemplateFieldClient) DeleteOneID(id uuid.UUID) *TemplateFieldDeleteOne {
|
||||
builder := c.Delete().Where(templatefield.ID(id))
|
||||
builder.mutation.id = &id
|
||||
builder.mutation.op = OpDeleteOne
|
||||
return &TemplateFieldDeleteOne{builder}
|
||||
}
|
||||
|
||||
// Query returns a query builder for TemplateField.
|
||||
func (c *TemplateFieldClient) Query() *TemplateFieldQuery {
|
||||
return &TemplateFieldQuery{
|
||||
config: c.config,
|
||||
ctx: &QueryContext{Type: TypeTemplateField},
|
||||
inters: c.Interceptors(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a TemplateField entity by its id.
|
||||
func (c *TemplateFieldClient) Get(ctx context.Context, id uuid.UUID) (*TemplateField, error) {
|
||||
return c.Query().Where(templatefield.ID(id)).Only(ctx)
|
||||
}
|
||||
|
||||
// GetX is like Get, but panics if an error occurs.
|
||||
func (c *TemplateFieldClient) GetX(ctx context.Context, id uuid.UUID) *TemplateField {
|
||||
obj, err := c.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// QueryItemTemplate queries the item_template edge of a TemplateField.
|
||||
func (c *TemplateFieldClient) QueryItemTemplate(_m *TemplateField) *ItemTemplateQuery {
|
||||
query := (&ItemTemplateClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(templatefield.Table, templatefield.FieldID, id),
|
||||
sqlgraph.To(itemtemplate.Table, itemtemplate.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, templatefield.ItemTemplateTable, templatefield.ItemTemplateColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *TemplateFieldClient) Hooks() []Hook {
|
||||
return c.hooks.TemplateField
|
||||
}
|
||||
|
||||
// Interceptors returns the client interceptors.
|
||||
func (c *TemplateFieldClient) Interceptors() []Interceptor {
|
||||
return c.inters.TemplateField
|
||||
}
|
||||
|
||||
func (c *TemplateFieldClient) mutate(ctx context.Context, m *TemplateFieldMutation) (Value, error) {
|
||||
switch m.Op() {
|
||||
case OpCreate:
|
||||
return (&TemplateFieldCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdate:
|
||||
return (&TemplateFieldUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdateOne:
|
||||
return (&TemplateFieldUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpDelete, OpDeleteOne:
|
||||
return (&TemplateFieldDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("ent: unknown TemplateField mutation op: %q", m.Op())
|
||||
}
|
||||
}
|
||||
|
||||
// UserClient is a client for the User schema.
|
||||
type UserClient struct {
|
||||
config
|
||||
@@ -2426,10 +2788,12 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error)
|
||||
type (
|
||||
hooks struct {
|
||||
Attachment, AuthRoles, AuthTokens, Group, GroupInvitationToken, Item, ItemField,
|
||||
Label, Location, MaintenanceEntry, Notifier, User []ent.Hook
|
||||
ItemTemplate, Label, Location, MaintenanceEntry, Notifier, TemplateField,
|
||||
User []ent.Hook
|
||||
}
|
||||
inters struct {
|
||||
Attachment, AuthRoles, AuthTokens, Group, GroupInvitationToken, Item, ItemField,
|
||||
Label, Location, MaintenanceEntry, Notifier, User []ent.Interceptor
|
||||
ItemTemplate, Label, Location, MaintenanceEntry, Notifier, TemplateField,
|
||||
User []ent.Interceptor
|
||||
}
|
||||
)
|
||||
|
||||
4
backend/internal/data/ent/ent.go
generated
@@ -19,10 +19,12 @@ 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/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/templatefield"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
|
||||
)
|
||||
|
||||
@@ -91,10 +93,12 @@ func checkColumn(t, c string) error {
|
||||
groupinvitationtoken.Table: groupinvitationtoken.ValidColumn,
|
||||
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,
|
||||
templatefield.Table: templatefield.ValidColumn,
|
||||
user.Table: user.ValidColumn,
|
||||
})
|
||||
})
|
||||
|
||||
18
backend/internal/data/ent/group.go
generated
@@ -46,9 +46,11 @@ type GroupEdges struct {
|
||||
InvitationTokens []*GroupInvitationToken `json:"invitation_tokens,omitempty"`
|
||||
// Notifiers holds the value of the notifiers edge.
|
||||
Notifiers []*Notifier `json:"notifiers,omitempty"`
|
||||
// ItemTemplates holds the value of the item_templates edge.
|
||||
ItemTemplates []*ItemTemplate `json:"item_templates,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [6]bool
|
||||
loadedTypes [7]bool
|
||||
}
|
||||
|
||||
// UsersOrErr returns the Users value or an error if the edge
|
||||
@@ -105,6 +107,15 @@ func (e GroupEdges) NotifiersOrErr() ([]*Notifier, error) {
|
||||
return nil, &NotLoadedError{edge: "notifiers"}
|
||||
}
|
||||
|
||||
// ItemTemplatesOrErr returns the ItemTemplates value or an error if the edge
|
||||
// was not loaded in eager-loading.
|
||||
func (e GroupEdges) ItemTemplatesOrErr() ([]*ItemTemplate, error) {
|
||||
if e.loadedTypes[6] {
|
||||
return e.ItemTemplates, nil
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "item_templates"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*Group) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
@@ -204,6 +215,11 @@ func (_m *Group) QueryNotifiers() *NotifierQuery {
|
||||
return NewGroupClient(_m.config).QueryNotifiers(_m)
|
||||
}
|
||||
|
||||
// QueryItemTemplates queries the "item_templates" edge of the Group entity.
|
||||
func (_m *Group) QueryItemTemplates() *ItemTemplateQuery {
|
||||
return NewGroupClient(_m.config).QueryItemTemplates(_m)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this Group.
|
||||
// Note that you need to call Group.Unwrap() before calling this method if this Group
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
|
||||
30
backend/internal/data/ent/group/group.go
generated
@@ -35,6 +35,8 @@ const (
|
||||
EdgeInvitationTokens = "invitation_tokens"
|
||||
// EdgeNotifiers holds the string denoting the notifiers edge name in mutations.
|
||||
EdgeNotifiers = "notifiers"
|
||||
// EdgeItemTemplates holds the string denoting the item_templates edge name in mutations.
|
||||
EdgeItemTemplates = "item_templates"
|
||||
// Table holds the table name of the group in the database.
|
||||
Table = "groups"
|
||||
// UsersTable is the table that holds the users relation/edge.
|
||||
@@ -79,6 +81,13 @@ const (
|
||||
NotifiersInverseTable = "notifiers"
|
||||
// NotifiersColumn is the table column denoting the notifiers relation/edge.
|
||||
NotifiersColumn = "group_id"
|
||||
// ItemTemplatesTable is the table that holds the item_templates relation/edge.
|
||||
ItemTemplatesTable = "item_templates"
|
||||
// ItemTemplatesInverseTable is the table name for the ItemTemplate entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "itemtemplate" package.
|
||||
ItemTemplatesInverseTable = "item_templates"
|
||||
// ItemTemplatesColumn is the table column denoting the item_templates relation/edge.
|
||||
ItemTemplatesColumn = "group_item_templates"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for group fields.
|
||||
@@ -226,6 +235,20 @@ func ByNotifiers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||
sqlgraph.OrderByNeighborTerms(s, newNotifiersStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||
}
|
||||
}
|
||||
|
||||
// ByItemTemplatesCount orders the results by item_templates count.
|
||||
func ByItemTemplatesCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborsCount(s, newItemTemplatesStep(), opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// ByItemTemplates orders the results by item_templates terms.
|
||||
func ByItemTemplates(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newItemTemplatesStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||
}
|
||||
}
|
||||
func newUsersStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
@@ -268,3 +291,10 @@ func newNotifiersStep() *sqlgraph.Step {
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn),
|
||||
)
|
||||
}
|
||||
func newItemTemplatesStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(ItemTemplatesInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, ItemTemplatesTable, ItemTemplatesColumn),
|
||||
)
|
||||
}
|
||||
|
||||
23
backend/internal/data/ent/group/where.go
generated
@@ -424,6 +424,29 @@ func HasNotifiersWith(preds ...predicate.Notifier) predicate.Group {
|
||||
})
|
||||
}
|
||||
|
||||
// HasItemTemplates applies the HasEdge predicate on the "item_templates" edge.
|
||||
func HasItemTemplates() predicate.Group {
|
||||
return predicate.Group(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, ItemTemplatesTable, ItemTemplatesColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasItemTemplatesWith applies the HasEdge predicate on the "item_templates" edge with a given conditions (other predicates).
|
||||
func HasItemTemplatesWith(preds ...predicate.ItemTemplate) predicate.Group {
|
||||
return predicate.Group(func(s *sql.Selector) {
|
||||
step := newItemTemplatesStep()
|
||||
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.Group) predicate.Group {
|
||||
return predicate.Group(sql.AndPredicates(predicates...))
|
||||
|
||||
32
backend/internal/data/ent/group_create.go
generated
@@ -14,6 +14,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/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"
|
||||
@@ -179,6 +180,21 @@ func (_c *GroupCreate) AddNotifiers(v ...*Notifier) *GroupCreate {
|
||||
return _c.AddNotifierIDs(ids...)
|
||||
}
|
||||
|
||||
// AddItemTemplateIDs adds the "item_templates" edge to the ItemTemplate entity by IDs.
|
||||
func (_c *GroupCreate) AddItemTemplateIDs(ids ...uuid.UUID) *GroupCreate {
|
||||
_c.mutation.AddItemTemplateIDs(ids...)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddItemTemplates adds the "item_templates" edges to the ItemTemplate entity.
|
||||
func (_c *GroupCreate) AddItemTemplates(v ...*ItemTemplate) *GroupCreate {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _c.AddItemTemplateIDs(ids...)
|
||||
}
|
||||
|
||||
// Mutation returns the GroupMutation object of the builder.
|
||||
func (_c *GroupCreate) Mutation() *GroupMutation {
|
||||
return _c.mutation
|
||||
@@ -398,6 +414,22 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
||||
}
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
if nodes := _c.mutation.ItemTemplatesIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.ItemTemplatesTable,
|
||||
Columns: []string{group.ItemTemplatesColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
return _node, _spec
|
||||
}
|
||||
|
||||
|
||||
77
backend/internal/data/ent/group_query.go
generated
@@ -16,6 +16,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/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"
|
||||
@@ -36,6 +37,7 @@ type GroupQuery struct {
|
||||
withLabels *LabelQuery
|
||||
withInvitationTokens *GroupInvitationTokenQuery
|
||||
withNotifiers *NotifierQuery
|
||||
withItemTemplates *ItemTemplateQuery
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
@@ -204,6 +206,28 @@ func (_q *GroupQuery) QueryNotifiers() *NotifierQuery {
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryItemTemplates chains the current query on the "item_templates" edge.
|
||||
func (_q *GroupQuery) QueryItemTemplates() *ItemTemplateQuery {
|
||||
query := (&ItemTemplateClient{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
|
||||
}
|
||||
selector := _q.sqlQuery(ctx)
|
||||
if err := selector.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(group.Table, group.FieldID, selector),
|
||||
sqlgraph.To(itemtemplate.Table, itemtemplate.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, group.ItemTemplatesTable, group.ItemTemplatesColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// First returns the first Group entity from the query.
|
||||
// Returns a *NotFoundError when no Group was found.
|
||||
func (_q *GroupQuery) First(ctx context.Context) (*Group, error) {
|
||||
@@ -402,6 +426,7 @@ func (_q *GroupQuery) Clone() *GroupQuery {
|
||||
withLabels: _q.withLabels.Clone(),
|
||||
withInvitationTokens: _q.withInvitationTokens.Clone(),
|
||||
withNotifiers: _q.withNotifiers.Clone(),
|
||||
withItemTemplates: _q.withItemTemplates.Clone(),
|
||||
// clone intermediate query.
|
||||
sql: _q.sql.Clone(),
|
||||
path: _q.path,
|
||||
@@ -474,6 +499,17 @@ func (_q *GroupQuery) WithNotifiers(opts ...func(*NotifierQuery)) *GroupQuery {
|
||||
return _q
|
||||
}
|
||||
|
||||
// WithItemTemplates tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "item_templates" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *GroupQuery) WithItemTemplates(opts ...func(*ItemTemplateQuery)) *GroupQuery {
|
||||
query := (&ItemTemplateClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withItemTemplates = query
|
||||
return _q
|
||||
}
|
||||
|
||||
// GroupBy is used to group vertices by one or more fields/columns.
|
||||
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||
//
|
||||
@@ -552,13 +588,14 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
|
||||
var (
|
||||
nodes = []*Group{}
|
||||
_spec = _q.querySpec()
|
||||
loadedTypes = [6]bool{
|
||||
loadedTypes = [7]bool{
|
||||
_q.withUsers != nil,
|
||||
_q.withLocations != nil,
|
||||
_q.withItems != nil,
|
||||
_q.withLabels != nil,
|
||||
_q.withInvitationTokens != nil,
|
||||
_q.withNotifiers != nil,
|
||||
_q.withItemTemplates != nil,
|
||||
}
|
||||
)
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
@@ -623,6 +660,13 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if query := _q.withItemTemplates; query != nil {
|
||||
if err := _q.loadItemTemplates(ctx, query, nodes,
|
||||
func(n *Group) { n.Edges.ItemTemplates = []*ItemTemplate{} },
|
||||
func(n *Group, e *ItemTemplate) { n.Edges.ItemTemplates = append(n.Edges.ItemTemplates, e) }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
@@ -811,6 +855,37 @@ func (_q *GroupQuery) loadNotifiers(ctx context.Context, query *NotifierQuery, n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (_q *GroupQuery) loadItemTemplates(ctx context.Context, query *ItemTemplateQuery, nodes []*Group, init func(*Group), assign func(*Group, *ItemTemplate)) error {
|
||||
fks := make([]driver.Value, 0, len(nodes))
|
||||
nodeids := make(map[uuid.UUID]*Group)
|
||||
for i := range nodes {
|
||||
fks = append(fks, nodes[i].ID)
|
||||
nodeids[nodes[i].ID] = nodes[i]
|
||||
if init != nil {
|
||||
init(nodes[i])
|
||||
}
|
||||
}
|
||||
query.withFKs = true
|
||||
query.Where(predicate.ItemTemplate(func(s *sql.Selector) {
|
||||
s.Where(sql.InValues(s.C(group.ItemTemplatesColumn), fks...))
|
||||
}))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
fk := n.group_item_templates
|
||||
if fk == nil {
|
||||
return fmt.Errorf(`foreign-key "group_item_templates" is nil for node %v`, n.ID)
|
||||
}
|
||||
node, ok := nodeids[*fk]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected referenced foreign-key "group_item_templates" returned %v for node %v`, *fk, n.ID)
|
||||
}
|
||||
assign(node, n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *GroupQuery) sqlCount(ctx context.Context) (int, error) {
|
||||
_spec := _q.querySpec()
|
||||
|
||||
163
backend/internal/data/ent/group_update.go
generated
@@ -15,6 +15,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/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"
|
||||
@@ -159,6 +160,21 @@ func (_u *GroupUpdate) AddNotifiers(v ...*Notifier) *GroupUpdate {
|
||||
return _u.AddNotifierIDs(ids...)
|
||||
}
|
||||
|
||||
// AddItemTemplateIDs adds the "item_templates" edge to the ItemTemplate entity by IDs.
|
||||
func (_u *GroupUpdate) AddItemTemplateIDs(ids ...uuid.UUID) *GroupUpdate {
|
||||
_u.mutation.AddItemTemplateIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddItemTemplates adds the "item_templates" edges to the ItemTemplate entity.
|
||||
func (_u *GroupUpdate) AddItemTemplates(v ...*ItemTemplate) *GroupUpdate {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.AddItemTemplateIDs(ids...)
|
||||
}
|
||||
|
||||
// Mutation returns the GroupMutation object of the builder.
|
||||
func (_u *GroupUpdate) Mutation() *GroupMutation {
|
||||
return _u.mutation
|
||||
@@ -290,6 +306,27 @@ func (_u *GroupUpdate) RemoveNotifiers(v ...*Notifier) *GroupUpdate {
|
||||
return _u.RemoveNotifierIDs(ids...)
|
||||
}
|
||||
|
||||
// ClearItemTemplates clears all "item_templates" edges to the ItemTemplate entity.
|
||||
func (_u *GroupUpdate) ClearItemTemplates() *GroupUpdate {
|
||||
_u.mutation.ClearItemTemplates()
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveItemTemplateIDs removes the "item_templates" edge to ItemTemplate entities by IDs.
|
||||
func (_u *GroupUpdate) RemoveItemTemplateIDs(ids ...uuid.UUID) *GroupUpdate {
|
||||
_u.mutation.RemoveItemTemplateIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveItemTemplates removes "item_templates" edges to ItemTemplate entities.
|
||||
func (_u *GroupUpdate) RemoveItemTemplates(v ...*ItemTemplate) *GroupUpdate {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.RemoveItemTemplateIDs(ids...)
|
||||
}
|
||||
|
||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||
func (_u *GroupUpdate) Save(ctx context.Context) (int, error) {
|
||||
_u.defaults()
|
||||
@@ -627,6 +664,51 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
if _u.mutation.ItemTemplatesCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.ItemTemplatesTable,
|
||||
Columns: []string{group.ItemTemplatesColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.RemovedItemTemplatesIDs(); len(nodes) > 0 && !_u.mutation.ItemTemplatesCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.ItemTemplatesTable,
|
||||
Columns: []string{group.ItemTemplatesColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.ItemTemplatesIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.ItemTemplatesTable,
|
||||
Columns: []string{group.ItemTemplatesColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{group.Label}
|
||||
@@ -771,6 +853,21 @@ func (_u *GroupUpdateOne) AddNotifiers(v ...*Notifier) *GroupUpdateOne {
|
||||
return _u.AddNotifierIDs(ids...)
|
||||
}
|
||||
|
||||
// AddItemTemplateIDs adds the "item_templates" edge to the ItemTemplate entity by IDs.
|
||||
func (_u *GroupUpdateOne) AddItemTemplateIDs(ids ...uuid.UUID) *GroupUpdateOne {
|
||||
_u.mutation.AddItemTemplateIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddItemTemplates adds the "item_templates" edges to the ItemTemplate entity.
|
||||
func (_u *GroupUpdateOne) AddItemTemplates(v ...*ItemTemplate) *GroupUpdateOne {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.AddItemTemplateIDs(ids...)
|
||||
}
|
||||
|
||||
// Mutation returns the GroupMutation object of the builder.
|
||||
func (_u *GroupUpdateOne) Mutation() *GroupMutation {
|
||||
return _u.mutation
|
||||
@@ -902,6 +999,27 @@ func (_u *GroupUpdateOne) RemoveNotifiers(v ...*Notifier) *GroupUpdateOne {
|
||||
return _u.RemoveNotifierIDs(ids...)
|
||||
}
|
||||
|
||||
// ClearItemTemplates clears all "item_templates" edges to the ItemTemplate entity.
|
||||
func (_u *GroupUpdateOne) ClearItemTemplates() *GroupUpdateOne {
|
||||
_u.mutation.ClearItemTemplates()
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveItemTemplateIDs removes the "item_templates" edge to ItemTemplate entities by IDs.
|
||||
func (_u *GroupUpdateOne) RemoveItemTemplateIDs(ids ...uuid.UUID) *GroupUpdateOne {
|
||||
_u.mutation.RemoveItemTemplateIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveItemTemplates removes "item_templates" edges to ItemTemplate entities.
|
||||
func (_u *GroupUpdateOne) RemoveItemTemplates(v ...*ItemTemplate) *GroupUpdateOne {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.RemoveItemTemplateIDs(ids...)
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the GroupUpdate builder.
|
||||
func (_u *GroupUpdateOne) Where(ps ...predicate.Group) *GroupUpdateOne {
|
||||
_u.mutation.Where(ps...)
|
||||
@@ -1269,6 +1387,51 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
if _u.mutation.ItemTemplatesCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.ItemTemplatesTable,
|
||||
Columns: []string{group.ItemTemplatesColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.RemovedItemTemplatesIDs(); len(nodes) > 0 && !_u.mutation.ItemTemplatesCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.ItemTemplatesTable,
|
||||
Columns: []string{group.ItemTemplatesColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.ItemTemplatesIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.ItemTemplatesTable,
|
||||
Columns: []string{group.ItemTemplatesColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
_node = &Group{config: _u.config}
|
||||
_spec.Assign = _node.assignValues
|
||||
_spec.ScanValues = _node.scanValues
|
||||
|
||||
8
backend/internal/data/ent/has_id.go
generated
@@ -32,6 +32,10 @@ func (_m *ItemField) GetID() uuid.UUID {
|
||||
return _m.ID
|
||||
}
|
||||
|
||||
func (_m *ItemTemplate) GetID() uuid.UUID {
|
||||
return _m.ID
|
||||
}
|
||||
|
||||
func (_m *Label) GetID() uuid.UUID {
|
||||
return _m.ID
|
||||
}
|
||||
@@ -48,6 +52,10 @@ func (_m *Notifier) GetID() uuid.UUID {
|
||||
return _m.ID
|
||||
}
|
||||
|
||||
func (_m *TemplateField) GetID() uuid.UUID {
|
||||
return _m.ID
|
||||
}
|
||||
|
||||
func (_m *User) GetID() uuid.UUID {
|
||||
return _m.ID
|
||||
}
|
||||
|
||||
24
backend/internal/data/ent/hook/hook.go
generated
@@ -93,6 +93,18 @@ func (f ItemFieldFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, e
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ItemFieldMutation", m)
|
||||
}
|
||||
|
||||
// The ItemTemplateFunc type is an adapter to allow the use of ordinary
|
||||
// function as ItemTemplate mutator.
|
||||
type ItemTemplateFunc func(context.Context, *ent.ItemTemplateMutation) (ent.Value, error)
|
||||
|
||||
// Mutate calls f(ctx, m).
|
||||
func (f ItemTemplateFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||
if mv, ok := m.(*ent.ItemTemplateMutation); ok {
|
||||
return f(ctx, mv)
|
||||
}
|
||||
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)
|
||||
@@ -141,6 +153,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 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)
|
||||
|
||||
// Mutate calls f(ctx, m).
|
||||
func (f TemplateFieldFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||
if mv, ok := m.(*ent.TemplateFieldMutation); ok {
|
||||
return f(ctx, mv)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.TemplateFieldMutation", m)
|
||||
}
|
||||
|
||||
// The UserFunc type is an adapter to allow the use of ordinary
|
||||
// function as User mutator.
|
||||
type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error)
|
||||
|
||||
5
backend/internal/data/ent/item_predicates.go
generated
@@ -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.)
|
||||
|
||||
376
backend/internal/data/ent/itemtemplate.go
generated
Normal file
@@ -0,0 +1,376 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"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/itemtemplate"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/location"
|
||||
)
|
||||
|
||||
// ItemTemplate is the model entity for the ItemTemplate schema.
|
||||
type ItemTemplate struct {
|
||||
config `json:"-"`
|
||||
// ID of the ent.
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
// CreatedAt holds the value of the "created_at" field.
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// UpdatedAt holds the value of the "updated_at" field.
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
// Name holds the value of the "name" field.
|
||||
Name string `json:"name,omitempty"`
|
||||
// Description holds the value of the "description" field.
|
||||
Description string `json:"description,omitempty"`
|
||||
// Notes holds the value of the "notes" field.
|
||||
Notes string `json:"notes,omitempty"`
|
||||
// DefaultQuantity holds the value of the "default_quantity" field.
|
||||
DefaultQuantity int `json:"default_quantity,omitempty"`
|
||||
// DefaultInsured holds the value of the "default_insured" field.
|
||||
DefaultInsured bool `json:"default_insured,omitempty"`
|
||||
// Default name template for items (can use placeholders)
|
||||
DefaultName string `json:"default_name,omitempty"`
|
||||
// Default description for items created from this template
|
||||
DefaultDescription string `json:"default_description,omitempty"`
|
||||
// DefaultManufacturer holds the value of the "default_manufacturer" field.
|
||||
DefaultManufacturer string `json:"default_manufacturer,omitempty"`
|
||||
// Default model number for items created from this template
|
||||
DefaultModelNumber string `json:"default_model_number,omitempty"`
|
||||
// DefaultLifetimeWarranty holds the value of the "default_lifetime_warranty" field.
|
||||
DefaultLifetimeWarranty bool `json:"default_lifetime_warranty,omitempty"`
|
||||
// DefaultWarrantyDetails holds the value of the "default_warranty_details" field.
|
||||
DefaultWarrantyDetails string `json:"default_warranty_details,omitempty"`
|
||||
// Whether to include warranty fields in items created from this template
|
||||
IncludeWarrantyFields bool `json:"include_warranty_fields,omitempty"`
|
||||
// Whether to include purchase fields in items created from this template
|
||||
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"`
|
||||
// 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"`
|
||||
group_item_templates *uuid.UUID
|
||||
item_template_location *uuid.UUID
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// ItemTemplateEdges holds the relations/edges for other nodes in the graph.
|
||||
type ItemTemplateEdges struct {
|
||||
// Group holds the value of the group edge.
|
||||
Group *Group `json:"group,omitempty"`
|
||||
// Fields holds the value of the fields edge.
|
||||
Fields []*TemplateField `json:"fields,omitempty"`
|
||||
// Location holds the value of the location edge.
|
||||
Location *Location `json:"location,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [3]bool
|
||||
}
|
||||
|
||||
// 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 ItemTemplateEdges) GroupOrErr() (*Group, error) {
|
||||
if e.Group != nil {
|
||||
return e.Group, nil
|
||||
} else if e.loadedTypes[0] {
|
||||
return nil, &NotFoundError{label: group.Label}
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "group"}
|
||||
}
|
||||
|
||||
// FieldsOrErr returns the Fields value or an error if the edge
|
||||
// was not loaded in eager-loading.
|
||||
func (e ItemTemplateEdges) FieldsOrErr() ([]*TemplateField, error) {
|
||||
if e.loadedTypes[1] {
|
||||
return e.Fields, nil
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "fields"}
|
||||
}
|
||||
|
||||
// LocationOrErr returns the Location value or an error if the edge
|
||||
// was not loaded in eager-loading, or loaded but was not found.
|
||||
func (e ItemTemplateEdges) LocationOrErr() (*Location, error) {
|
||||
if e.Location != nil {
|
||||
return e.Location, nil
|
||||
} else if e.loadedTypes[2] {
|
||||
return nil, &NotFoundError{label: location.Label}
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "location"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*ItemTemplate) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case itemtemplate.FieldDefaultLabelIds:
|
||||
values[i] = new([]byte)
|
||||
case itemtemplate.FieldDefaultInsured, itemtemplate.FieldDefaultLifetimeWarranty, itemtemplate.FieldIncludeWarrantyFields, itemtemplate.FieldIncludePurchaseFields, itemtemplate.FieldIncludeSoldFields:
|
||||
values[i] = new(sql.NullBool)
|
||||
case itemtemplate.FieldDefaultQuantity:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case itemtemplate.FieldName, itemtemplate.FieldDescription, itemtemplate.FieldNotes, itemtemplate.FieldDefaultName, itemtemplate.FieldDefaultDescription, itemtemplate.FieldDefaultManufacturer, itemtemplate.FieldDefaultModelNumber, itemtemplate.FieldDefaultWarrantyDetails:
|
||||
values[i] = new(sql.NullString)
|
||||
case itemtemplate.FieldCreatedAt, itemtemplate.FieldUpdatedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
case itemtemplate.FieldID:
|
||||
values[i] = new(uuid.UUID)
|
||||
case itemtemplate.ForeignKeys[0]: // group_item_templates
|
||||
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
|
||||
case itemtemplate.ForeignKeys[1]: // item_template_location
|
||||
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the ItemTemplate fields.
|
||||
func (_m *ItemTemplate) 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 itemtemplate.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 itemtemplate.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 itemtemplate.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 itemtemplate.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 itemtemplate.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 itemtemplate.FieldNotes:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field notes", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Notes = value.String
|
||||
}
|
||||
case itemtemplate.FieldDefaultQuantity:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_quantity", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultQuantity = int(value.Int64)
|
||||
}
|
||||
case itemtemplate.FieldDefaultInsured:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_insured", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultInsured = value.Bool
|
||||
}
|
||||
case itemtemplate.FieldDefaultName:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_name", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultName = value.String
|
||||
}
|
||||
case itemtemplate.FieldDefaultDescription:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_description", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultDescription = value.String
|
||||
}
|
||||
case itemtemplate.FieldDefaultManufacturer:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_manufacturer", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultManufacturer = value.String
|
||||
}
|
||||
case itemtemplate.FieldDefaultModelNumber:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_model_number", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultModelNumber = value.String
|
||||
}
|
||||
case itemtemplate.FieldDefaultLifetimeWarranty:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_lifetime_warranty", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultLifetimeWarranty = value.Bool
|
||||
}
|
||||
case itemtemplate.FieldDefaultWarrantyDetails:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_warranty_details", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultWarrantyDetails = value.String
|
||||
}
|
||||
case itemtemplate.FieldIncludeWarrantyFields:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field include_warranty_fields", values[i])
|
||||
} else if value.Valid {
|
||||
_m.IncludeWarrantyFields = value.Bool
|
||||
}
|
||||
case itemtemplate.FieldIncludePurchaseFields:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field include_purchase_fields", values[i])
|
||||
} else if value.Valid {
|
||||
_m.IncludePurchaseFields = value.Bool
|
||||
}
|
||||
case itemtemplate.FieldIncludeSoldFields:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field include_sold_fields", values[i])
|
||||
} else if value.Valid {
|
||||
_m.IncludeSoldFields = value.Bool
|
||||
}
|
||||
case itemtemplate.FieldDefaultLabelIds:
|
||||
if value, ok := values[i].(*[]byte); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_label_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)
|
||||
}
|
||||
}
|
||||
case itemtemplate.ForeignKeys[0]:
|
||||
if value, ok := values[i].(*sql.NullScanner); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field group_item_templates", values[i])
|
||||
} else if value.Valid {
|
||||
_m.group_item_templates = new(uuid.UUID)
|
||||
*_m.group_item_templates = *value.S.(*uuid.UUID)
|
||||
}
|
||||
case itemtemplate.ForeignKeys[1]:
|
||||
if value, ok := values[i].(*sql.NullScanner); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field item_template_location", values[i])
|
||||
} else if value.Valid {
|
||||
_m.item_template_location = new(uuid.UUID)
|
||||
*_m.item_template_location = *value.S.(*uuid.UUID)
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the ent.Value that was dynamically selected and assigned to the ItemTemplate.
|
||||
// This includes values selected through modifiers, order, etc.
|
||||
func (_m *ItemTemplate) Value(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// QueryGroup queries the "group" edge of the ItemTemplate entity.
|
||||
func (_m *ItemTemplate) QueryGroup() *GroupQuery {
|
||||
return NewItemTemplateClient(_m.config).QueryGroup(_m)
|
||||
}
|
||||
|
||||
// QueryFields queries the "fields" edge of the ItemTemplate entity.
|
||||
func (_m *ItemTemplate) QueryFields() *TemplateFieldQuery {
|
||||
return NewItemTemplateClient(_m.config).QueryFields(_m)
|
||||
}
|
||||
|
||||
// QueryLocation queries the "location" edge of the ItemTemplate entity.
|
||||
func (_m *ItemTemplate) QueryLocation() *LocationQuery {
|
||||
return NewItemTemplateClient(_m.config).QueryLocation(_m)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this ItemTemplate.
|
||||
// Note that you need to call ItemTemplate.Unwrap() before calling this method if this ItemTemplate
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (_m *ItemTemplate) Update() *ItemTemplateUpdateOne {
|
||||
return NewItemTemplateClient(_m.config).UpdateOne(_m)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the ItemTemplate 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 *ItemTemplate) Unwrap() *ItemTemplate {
|
||||
_tx, ok := _m.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: ItemTemplate is not a transactional entity")
|
||||
}
|
||||
_m.config.driver = _tx.drv
|
||||
return _m
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (_m *ItemTemplate) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("ItemTemplate(")
|
||||
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||
builder.WriteString("created_at=")
|
||||
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("updated_at=")
|
||||
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("name=")
|
||||
builder.WriteString(_m.Name)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("description=")
|
||||
builder.WriteString(_m.Description)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("notes=")
|
||||
builder.WriteString(_m.Notes)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_quantity=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.DefaultQuantity))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_insured=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.DefaultInsured))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_name=")
|
||||
builder.WriteString(_m.DefaultName)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_description=")
|
||||
builder.WriteString(_m.DefaultDescription)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_manufacturer=")
|
||||
builder.WriteString(_m.DefaultManufacturer)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_model_number=")
|
||||
builder.WriteString(_m.DefaultModelNumber)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_lifetime_warranty=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.DefaultLifetimeWarranty))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("default_warranty_details=")
|
||||
builder.WriteString(_m.DefaultWarrantyDetails)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("include_warranty_fields=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.IncludeWarrantyFields))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("include_purchase_fields=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.IncludePurchaseFields))
|
||||
builder.WriteString(", ")
|
||||
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.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// ItemTemplates is a parsable slice of ItemTemplate.
|
||||
type ItemTemplates []*ItemTemplate
|
||||
301
backend/internal/data/ent/itemtemplate/itemtemplate.go
generated
Normal file
@@ -0,0 +1,301 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package itemtemplate
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the itemtemplate type in the database.
|
||||
Label = "item_template"
|
||||
// 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.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldName holds the string denoting the name field in the database.
|
||||
FieldName = "name"
|
||||
// FieldDescription holds the string denoting the description field in the database.
|
||||
FieldDescription = "description"
|
||||
// FieldNotes holds the string denoting the notes field in the database.
|
||||
FieldNotes = "notes"
|
||||
// FieldDefaultQuantity holds the string denoting the default_quantity field in the database.
|
||||
FieldDefaultQuantity = "default_quantity"
|
||||
// FieldDefaultInsured holds the string denoting the default_insured field in the database.
|
||||
FieldDefaultInsured = "default_insured"
|
||||
// FieldDefaultName holds the string denoting the default_name field in the database.
|
||||
FieldDefaultName = "default_name"
|
||||
// FieldDefaultDescription holds the string denoting the default_description field in the database.
|
||||
FieldDefaultDescription = "default_description"
|
||||
// FieldDefaultManufacturer holds the string denoting the default_manufacturer field in the database.
|
||||
FieldDefaultManufacturer = "default_manufacturer"
|
||||
// FieldDefaultModelNumber holds the string denoting the default_model_number field in the database.
|
||||
FieldDefaultModelNumber = "default_model_number"
|
||||
// FieldDefaultLifetimeWarranty holds the string denoting the default_lifetime_warranty field in the database.
|
||||
FieldDefaultLifetimeWarranty = "default_lifetime_warranty"
|
||||
// FieldDefaultWarrantyDetails holds the string denoting the default_warranty_details field in the database.
|
||||
FieldDefaultWarrantyDetails = "default_warranty_details"
|
||||
// FieldIncludeWarrantyFields holds the string denoting the include_warranty_fields field in the database.
|
||||
FieldIncludeWarrantyFields = "include_warranty_fields"
|
||||
// FieldIncludePurchaseFields holds the string denoting the include_purchase_fields field in the database.
|
||||
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"
|
||||
// EdgeGroup holds the string denoting the group edge name in mutations.
|
||||
EdgeGroup = "group"
|
||||
// EdgeFields holds the string denoting the fields edge name in mutations.
|
||||
EdgeFields = "fields"
|
||||
// EdgeLocation holds the string denoting the location edge name in mutations.
|
||||
EdgeLocation = "location"
|
||||
// Table holds the table name of the itemtemplate in the database.
|
||||
Table = "item_templates"
|
||||
// GroupTable is the table that holds the group relation/edge.
|
||||
GroupTable = "item_templates"
|
||||
// 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_item_templates"
|
||||
// FieldsTable is the table that holds the fields relation/edge.
|
||||
FieldsTable = "template_fields"
|
||||
// FieldsInverseTable is the table name for the TemplateField entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "templatefield" package.
|
||||
FieldsInverseTable = "template_fields"
|
||||
// FieldsColumn is the table column denoting the fields relation/edge.
|
||||
FieldsColumn = "item_template_fields"
|
||||
// LocationTable is the table that holds the location relation/edge.
|
||||
LocationTable = "item_templates"
|
||||
// LocationInverseTable is the table name for the Location entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "location" package.
|
||||
LocationInverseTable = "locations"
|
||||
// LocationColumn is the table column denoting the location relation/edge.
|
||||
LocationColumn = "item_template_location"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for itemtemplate fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldDescription,
|
||||
FieldNotes,
|
||||
FieldDefaultQuantity,
|
||||
FieldDefaultInsured,
|
||||
FieldDefaultName,
|
||||
FieldDefaultDescription,
|
||||
FieldDefaultManufacturer,
|
||||
FieldDefaultModelNumber,
|
||||
FieldDefaultLifetimeWarranty,
|
||||
FieldDefaultWarrantyDetails,
|
||||
FieldIncludeWarrantyFields,
|
||||
FieldIncludePurchaseFields,
|
||||
FieldIncludeSoldFields,
|
||||
FieldDefaultLabelIds,
|
||||
}
|
||||
|
||||
// ForeignKeys holds the SQL foreign-keys that are owned by the "item_templates"
|
||||
// table and are not defined as standalone fields in the schema.
|
||||
var ForeignKeys = []string{
|
||||
"group_item_templates",
|
||||
"item_template_location",
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ForeignKeys {
|
||||
if column == ForeignKeys[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
DescriptionValidator func(string) error
|
||||
// NotesValidator is a validator for the "notes" field. It is called by the builders before save.
|
||||
NotesValidator func(string) error
|
||||
// DefaultDefaultQuantity holds the default value on creation for the "default_quantity" field.
|
||||
DefaultDefaultQuantity int
|
||||
// DefaultDefaultInsured holds the default value on creation for the "default_insured" field.
|
||||
DefaultDefaultInsured bool
|
||||
// DefaultNameValidator is a validator for the "default_name" field. It is called by the builders before save.
|
||||
DefaultNameValidator func(string) error
|
||||
// DefaultDescriptionValidator is a validator for the "default_description" field. It is called by the builders before save.
|
||||
DefaultDescriptionValidator func(string) error
|
||||
// DefaultManufacturerValidator is a validator for the "default_manufacturer" field. It is called by the builders before save.
|
||||
DefaultManufacturerValidator func(string) error
|
||||
// DefaultModelNumberValidator is a validator for the "default_model_number" field. It is called by the builders before save.
|
||||
DefaultModelNumberValidator func(string) error
|
||||
// DefaultDefaultLifetimeWarranty holds the default value on creation for the "default_lifetime_warranty" field.
|
||||
DefaultDefaultLifetimeWarranty bool
|
||||
// DefaultWarrantyDetailsValidator is a validator for the "default_warranty_details" field. It is called by the builders before save.
|
||||
DefaultWarrantyDetailsValidator func(string) error
|
||||
// DefaultIncludeWarrantyFields holds the default value on creation for the "include_warranty_fields" field.
|
||||
DefaultIncludeWarrantyFields bool
|
||||
// DefaultIncludePurchaseFields holds the default value on creation for the "include_purchase_fields" field.
|
||||
DefaultIncludePurchaseFields bool
|
||||
// DefaultIncludeSoldFields holds the default value on creation for the "include_sold_fields" field.
|
||||
DefaultIncludeSoldFields bool
|
||||
// DefaultID holds the default value on creation for the "id" field.
|
||||
DefaultID func() uuid.UUID
|
||||
)
|
||||
|
||||
// OrderOption defines the ordering options for the ItemTemplate queries.
|
||||
type OrderOption func(*sql.Selector)
|
||||
|
||||
// ByID orders the results by the id field.
|
||||
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCreatedAt orders the results by the created_at field.
|
||||
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUpdatedAt orders the results by the updated_at field.
|
||||
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByName orders the results by the name field.
|
||||
func ByName(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldName, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDescription orders the results by the description field.
|
||||
func ByDescription(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDescription, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByNotes orders the results by the notes field.
|
||||
func ByNotes(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldNotes, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultQuantity orders the results by the default_quantity field.
|
||||
func ByDefaultQuantity(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultQuantity, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultInsured orders the results by the default_insured field.
|
||||
func ByDefaultInsured(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultInsured, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultName orders the results by the default_name field.
|
||||
func ByDefaultName(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultName, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultDescription orders the results by the default_description field.
|
||||
func ByDefaultDescription(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultDescription, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultManufacturer orders the results by the default_manufacturer field.
|
||||
func ByDefaultManufacturer(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultManufacturer, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultModelNumber orders the results by the default_model_number field.
|
||||
func ByDefaultModelNumber(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultModelNumber, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultLifetimeWarranty orders the results by the default_lifetime_warranty field.
|
||||
func ByDefaultLifetimeWarranty(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultLifetimeWarranty, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultWarrantyDetails orders the results by the default_warranty_details field.
|
||||
func ByDefaultWarrantyDetails(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultWarrantyDetails, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByIncludeWarrantyFields orders the results by the include_warranty_fields field.
|
||||
func ByIncludeWarrantyFields(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldIncludeWarrantyFields, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByIncludePurchaseFields orders the results by the include_purchase_fields field.
|
||||
func ByIncludePurchaseFields(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldIncludePurchaseFields, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByIncludeSoldFields orders the results by the include_sold_fields field.
|
||||
func ByIncludeSoldFields(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldIncludeSoldFields, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByGroupField orders the results by group field.
|
||||
func ByGroupField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newGroupStep(), sql.OrderByField(field, opts...))
|
||||
}
|
||||
}
|
||||
|
||||
// ByFieldsCount orders the results by fields count.
|
||||
func ByFieldsCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborsCount(s, newFieldsStep(), opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// ByFields orders the results by fields terms.
|
||||
func ByFields(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newFieldsStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||
}
|
||||
}
|
||||
|
||||
// ByLocationField orders the results by location field.
|
||||
func ByLocationField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newLocationStep(), sql.OrderByField(field, opts...))
|
||||
}
|
||||
}
|
||||
func newGroupStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(GroupInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
|
||||
)
|
||||
}
|
||||
func newFieldsStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(FieldsInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, FieldsTable, FieldsColumn),
|
||||
)
|
||||
}
|
||||
func newLocationStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(LocationInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, false, LocationTable, LocationColumn),
|
||||
)
|
||||
}
|
||||
991
backend/internal/data/ent/itemtemplate/where.go
generated
Normal file
@@ -0,0 +1,991 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package itemtemplate
|
||||
|
||||
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.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDEQ applies the EQ predicate on the ID field.
|
||||
func IDEQ(id uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDNEQ applies the NEQ predicate on the ID field.
|
||||
func IDNEQ(id uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDIn applies the In predicate on the ID field.
|
||||
func IDIn(ids ...uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDNotIn applies the NotIn predicate on the ID field.
|
||||
func IDNotIn(ids ...uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDGT applies the GT predicate on the ID field.
|
||||
func IDGT(id uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDGTE applies the GTE predicate on the ID field.
|
||||
func IDGTE(id uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLT applies the LT predicate on the ID field.
|
||||
func IDLT(id uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLTE applies the LTE predicate on the ID field.
|
||||
func IDLTE(id uuid.UUID) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(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.ItemTemplate {
|
||||
return predicate.ItemTemplate(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.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
|
||||
func Name(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
|
||||
func Description(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDescription, v))
|
||||
}
|
||||
|
||||
// Notes applies equality check predicate on the "notes" field. It's identical to NotesEQ.
|
||||
func Notes(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldNotes, v))
|
||||
}
|
||||
|
||||
// DefaultQuantity applies equality check predicate on the "default_quantity" field. It's identical to DefaultQuantityEQ.
|
||||
func DefaultQuantity(v int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultQuantity, v))
|
||||
}
|
||||
|
||||
// DefaultInsured applies equality check predicate on the "default_insured" field. It's identical to DefaultInsuredEQ.
|
||||
func DefaultInsured(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultInsured, v))
|
||||
}
|
||||
|
||||
// DefaultName applies equality check predicate on the "default_name" field. It's identical to DefaultNameEQ.
|
||||
func DefaultName(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultDescription applies equality check predicate on the "default_description" field. It's identical to DefaultDescriptionEQ.
|
||||
func DefaultDescription(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturer applies equality check predicate on the "default_manufacturer" field. It's identical to DefaultManufacturerEQ.
|
||||
func DefaultManufacturer(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumber applies equality check predicate on the "default_model_number" field. It's identical to DefaultModelNumberEQ.
|
||||
func DefaultModelNumber(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultLifetimeWarranty applies equality check predicate on the "default_lifetime_warranty" field. It's identical to DefaultLifetimeWarrantyEQ.
|
||||
func DefaultLifetimeWarranty(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultLifetimeWarranty, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetails applies equality check predicate on the "default_warranty_details" field. It's identical to DefaultWarrantyDetailsEQ.
|
||||
func DefaultWarrantyDetails(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// IncludeWarrantyFields applies equality check predicate on the "include_warranty_fields" field. It's identical to IncludeWarrantyFieldsEQ.
|
||||
func IncludeWarrantyFields(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldIncludeWarrantyFields, v))
|
||||
}
|
||||
|
||||
// IncludePurchaseFields applies equality check predicate on the "include_purchase_fields" field. It's identical to IncludePurchaseFieldsEQ.
|
||||
func IncludePurchaseFields(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldIncludePurchaseFields, v))
|
||||
}
|
||||
|
||||
// IncludeSoldFields applies equality check predicate on the "include_sold_fields" field. It's identical to IncludeSoldFieldsEQ.
|
||||
func IncludeSoldFields(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldIncludeSoldFields, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||
func CreatedAtNEQ(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||
func CreatedAtIn(vs ...time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||
func CreatedAtNotIn(vs ...time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||
func CreatedAtGT(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||
func CreatedAtGTE(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||
func CreatedAtLT(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||
func CreatedAtLTE(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||
func UpdatedAtEQ(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||
func UpdatedAtNEQ(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||
func UpdatedAtIn(vs ...time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||
func UpdatedAtNotIn(vs ...time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||
func UpdatedAtGT(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||
func UpdatedAtGTE(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||
func UpdatedAtLT(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||
func UpdatedAtLTE(v time.Time) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// NameEQ applies the EQ predicate on the "name" field.
|
||||
func NameEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// NameNEQ applies the NEQ predicate on the "name" field.
|
||||
func NameNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// NameIn applies the In predicate on the "name" field.
|
||||
func NameIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldName, vs...))
|
||||
}
|
||||
|
||||
// NameNotIn applies the NotIn predicate on the "name" field.
|
||||
func NameNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldName, vs...))
|
||||
}
|
||||
|
||||
// NameGT applies the GT predicate on the "name" field.
|
||||
func NameGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldName, v))
|
||||
}
|
||||
|
||||
// NameGTE applies the GTE predicate on the "name" field.
|
||||
func NameGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldName, v))
|
||||
}
|
||||
|
||||
// NameLT applies the LT predicate on the "name" field.
|
||||
func NameLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldName, v))
|
||||
}
|
||||
|
||||
// NameLTE applies the LTE predicate on the "name" field.
|
||||
func NameLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldName, v))
|
||||
}
|
||||
|
||||
// NameContains applies the Contains predicate on the "name" field.
|
||||
func NameContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldName, v))
|
||||
}
|
||||
|
||||
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
|
||||
func NameHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldName, v))
|
||||
}
|
||||
|
||||
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
|
||||
func NameHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldName, v))
|
||||
}
|
||||
|
||||
// NameEqualFold applies the EqualFold predicate on the "name" field.
|
||||
func NameEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldName, v))
|
||||
}
|
||||
|
||||
// NameContainsFold applies the ContainsFold predicate on the "name" field.
|
||||
func NameContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldName, v))
|
||||
}
|
||||
|
||||
// DescriptionEQ applies the EQ predicate on the "description" field.
|
||||
func DescriptionEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionNEQ applies the NEQ predicate on the "description" field.
|
||||
func DescriptionNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionIn applies the In predicate on the "description" field.
|
||||
func DescriptionIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldDescription, vs...))
|
||||
}
|
||||
|
||||
// DescriptionNotIn applies the NotIn predicate on the "description" field.
|
||||
func DescriptionNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldDescription, vs...))
|
||||
}
|
||||
|
||||
// DescriptionGT applies the GT predicate on the "description" field.
|
||||
func DescriptionGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionGTE applies the GTE predicate on the "description" field.
|
||||
func DescriptionGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionLT applies the LT predicate on the "description" field.
|
||||
func DescriptionLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionLTE applies the LTE predicate on the "description" field.
|
||||
func DescriptionLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionContains applies the Contains predicate on the "description" field.
|
||||
func DescriptionContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field.
|
||||
func DescriptionHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field.
|
||||
func DescriptionHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionIsNil applies the IsNil predicate on the "description" field.
|
||||
func DescriptionIsNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIsNull(FieldDescription))
|
||||
}
|
||||
|
||||
// DescriptionNotNil applies the NotNil predicate on the "description" field.
|
||||
func DescriptionNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldDescription))
|
||||
}
|
||||
|
||||
// DescriptionEqualFold applies the EqualFold predicate on the "description" field.
|
||||
func DescriptionEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldDescription, v))
|
||||
}
|
||||
|
||||
// DescriptionContainsFold applies the ContainsFold predicate on the "description" field.
|
||||
func DescriptionContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldDescription, v))
|
||||
}
|
||||
|
||||
// NotesEQ applies the EQ predicate on the "notes" field.
|
||||
func NotesEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesNEQ applies the NEQ predicate on the "notes" field.
|
||||
func NotesNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesIn applies the In predicate on the "notes" field.
|
||||
func NotesIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldNotes, vs...))
|
||||
}
|
||||
|
||||
// NotesNotIn applies the NotIn predicate on the "notes" field.
|
||||
func NotesNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldNotes, vs...))
|
||||
}
|
||||
|
||||
// NotesGT applies the GT predicate on the "notes" field.
|
||||
func NotesGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesGTE applies the GTE predicate on the "notes" field.
|
||||
func NotesGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesLT applies the LT predicate on the "notes" field.
|
||||
func NotesLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesLTE applies the LTE predicate on the "notes" field.
|
||||
func NotesLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesContains applies the Contains predicate on the "notes" field.
|
||||
func NotesContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesHasPrefix applies the HasPrefix predicate on the "notes" field.
|
||||
func NotesHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesHasSuffix applies the HasSuffix predicate on the "notes" field.
|
||||
func NotesHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesIsNil applies the IsNil predicate on the "notes" field.
|
||||
func NotesIsNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIsNull(FieldNotes))
|
||||
}
|
||||
|
||||
// NotesNotNil applies the NotNil predicate on the "notes" field.
|
||||
func NotesNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldNotes))
|
||||
}
|
||||
|
||||
// NotesEqualFold applies the EqualFold predicate on the "notes" field.
|
||||
func NotesEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldNotes, v))
|
||||
}
|
||||
|
||||
// NotesContainsFold applies the ContainsFold predicate on the "notes" field.
|
||||
func NotesContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldNotes, v))
|
||||
}
|
||||
|
||||
// DefaultQuantityEQ applies the EQ predicate on the "default_quantity" field.
|
||||
func DefaultQuantityEQ(v int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultQuantity, v))
|
||||
}
|
||||
|
||||
// DefaultQuantityNEQ applies the NEQ predicate on the "default_quantity" field.
|
||||
func DefaultQuantityNEQ(v int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultQuantity, v))
|
||||
}
|
||||
|
||||
// DefaultQuantityIn applies the In predicate on the "default_quantity" field.
|
||||
func DefaultQuantityIn(vs ...int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldDefaultQuantity, vs...))
|
||||
}
|
||||
|
||||
// DefaultQuantityNotIn applies the NotIn predicate on the "default_quantity" field.
|
||||
func DefaultQuantityNotIn(vs ...int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldDefaultQuantity, vs...))
|
||||
}
|
||||
|
||||
// DefaultQuantityGT applies the GT predicate on the "default_quantity" field.
|
||||
func DefaultQuantityGT(v int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldDefaultQuantity, v))
|
||||
}
|
||||
|
||||
// DefaultQuantityGTE applies the GTE predicate on the "default_quantity" field.
|
||||
func DefaultQuantityGTE(v int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldDefaultQuantity, v))
|
||||
}
|
||||
|
||||
// DefaultQuantityLT applies the LT predicate on the "default_quantity" field.
|
||||
func DefaultQuantityLT(v int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldDefaultQuantity, v))
|
||||
}
|
||||
|
||||
// DefaultQuantityLTE applies the LTE predicate on the "default_quantity" field.
|
||||
func DefaultQuantityLTE(v int) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldDefaultQuantity, v))
|
||||
}
|
||||
|
||||
// DefaultInsuredEQ applies the EQ predicate on the "default_insured" field.
|
||||
func DefaultInsuredEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultInsured, v))
|
||||
}
|
||||
|
||||
// DefaultInsuredNEQ applies the NEQ predicate on the "default_insured" field.
|
||||
func DefaultInsuredNEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultInsured, v))
|
||||
}
|
||||
|
||||
// DefaultNameEQ applies the EQ predicate on the "default_name" field.
|
||||
func DefaultNameEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameNEQ applies the NEQ predicate on the "default_name" field.
|
||||
func DefaultNameNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameIn applies the In predicate on the "default_name" field.
|
||||
func DefaultNameIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldDefaultName, vs...))
|
||||
}
|
||||
|
||||
// DefaultNameNotIn applies the NotIn predicate on the "default_name" field.
|
||||
func DefaultNameNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldDefaultName, vs...))
|
||||
}
|
||||
|
||||
// DefaultNameGT applies the GT predicate on the "default_name" field.
|
||||
func DefaultNameGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameGTE applies the GTE predicate on the "default_name" field.
|
||||
func DefaultNameGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameLT applies the LT predicate on the "default_name" field.
|
||||
func DefaultNameLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameLTE applies the LTE predicate on the "default_name" field.
|
||||
func DefaultNameLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameContains applies the Contains predicate on the "default_name" field.
|
||||
func DefaultNameContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameHasPrefix applies the HasPrefix predicate on the "default_name" field.
|
||||
func DefaultNameHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameHasSuffix applies the HasSuffix predicate on the "default_name" field.
|
||||
func DefaultNameHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameIsNil applies the IsNil predicate on the "default_name" field.
|
||||
func DefaultNameIsNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIsNull(FieldDefaultName))
|
||||
}
|
||||
|
||||
// DefaultNameNotNil applies the NotNil predicate on the "default_name" field.
|
||||
func DefaultNameNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultName))
|
||||
}
|
||||
|
||||
// DefaultNameEqualFold applies the EqualFold predicate on the "default_name" field.
|
||||
func DefaultNameEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultNameContainsFold applies the ContainsFold predicate on the "default_name" field.
|
||||
func DefaultNameContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldDefaultName, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionEQ applies the EQ predicate on the "default_description" field.
|
||||
func DefaultDescriptionEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionNEQ applies the NEQ predicate on the "default_description" field.
|
||||
func DefaultDescriptionNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionIn applies the In predicate on the "default_description" field.
|
||||
func DefaultDescriptionIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldDefaultDescription, vs...))
|
||||
}
|
||||
|
||||
// DefaultDescriptionNotIn applies the NotIn predicate on the "default_description" field.
|
||||
func DefaultDescriptionNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldDefaultDescription, vs...))
|
||||
}
|
||||
|
||||
// DefaultDescriptionGT applies the GT predicate on the "default_description" field.
|
||||
func DefaultDescriptionGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionGTE applies the GTE predicate on the "default_description" field.
|
||||
func DefaultDescriptionGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionLT applies the LT predicate on the "default_description" field.
|
||||
func DefaultDescriptionLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionLTE applies the LTE predicate on the "default_description" field.
|
||||
func DefaultDescriptionLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionContains applies the Contains predicate on the "default_description" field.
|
||||
func DefaultDescriptionContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionHasPrefix applies the HasPrefix predicate on the "default_description" field.
|
||||
func DefaultDescriptionHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionHasSuffix applies the HasSuffix predicate on the "default_description" field.
|
||||
func DefaultDescriptionHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionIsNil applies the IsNil predicate on the "default_description" field.
|
||||
func DefaultDescriptionIsNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIsNull(FieldDefaultDescription))
|
||||
}
|
||||
|
||||
// DefaultDescriptionNotNil applies the NotNil predicate on the "default_description" field.
|
||||
func DefaultDescriptionNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultDescription))
|
||||
}
|
||||
|
||||
// DefaultDescriptionEqualFold applies the EqualFold predicate on the "default_description" field.
|
||||
func DefaultDescriptionEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultDescriptionContainsFold applies the ContainsFold predicate on the "default_description" field.
|
||||
func DefaultDescriptionContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldDefaultDescription, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerEQ applies the EQ predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerNEQ applies the NEQ predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerIn applies the In predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldDefaultManufacturer, vs...))
|
||||
}
|
||||
|
||||
// DefaultManufacturerNotIn applies the NotIn predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldDefaultManufacturer, vs...))
|
||||
}
|
||||
|
||||
// DefaultManufacturerGT applies the GT predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerGTE applies the GTE predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerLT applies the LT predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerLTE applies the LTE predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerContains applies the Contains predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerHasPrefix applies the HasPrefix predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerHasSuffix applies the HasSuffix predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerIsNil applies the IsNil predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerIsNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIsNull(FieldDefaultManufacturer))
|
||||
}
|
||||
|
||||
// DefaultManufacturerNotNil applies the NotNil predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultManufacturer))
|
||||
}
|
||||
|
||||
// DefaultManufacturerEqualFold applies the EqualFold predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultManufacturerContainsFold applies the ContainsFold predicate on the "default_manufacturer" field.
|
||||
func DefaultManufacturerContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldDefaultManufacturer, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberEQ applies the EQ predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberNEQ applies the NEQ predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberIn applies the In predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldDefaultModelNumber, vs...))
|
||||
}
|
||||
|
||||
// DefaultModelNumberNotIn applies the NotIn predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldDefaultModelNumber, vs...))
|
||||
}
|
||||
|
||||
// DefaultModelNumberGT applies the GT predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberGTE applies the GTE predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberLT applies the LT predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberLTE applies the LTE predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberContains applies the Contains predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberHasPrefix applies the HasPrefix predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberHasSuffix applies the HasSuffix predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberIsNil applies the IsNil predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberIsNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIsNull(FieldDefaultModelNumber))
|
||||
}
|
||||
|
||||
// DefaultModelNumberNotNil applies the NotNil predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultModelNumber))
|
||||
}
|
||||
|
||||
// DefaultModelNumberEqualFold applies the EqualFold predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultModelNumberContainsFold applies the ContainsFold predicate on the "default_model_number" field.
|
||||
func DefaultModelNumberContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldDefaultModelNumber, v))
|
||||
}
|
||||
|
||||
// DefaultLifetimeWarrantyEQ applies the EQ predicate on the "default_lifetime_warranty" field.
|
||||
func DefaultLifetimeWarrantyEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultLifetimeWarranty, v))
|
||||
}
|
||||
|
||||
// DefaultLifetimeWarrantyNEQ applies the NEQ predicate on the "default_lifetime_warranty" field.
|
||||
func DefaultLifetimeWarrantyNEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultLifetimeWarranty, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsEQ applies the EQ predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsNEQ applies the NEQ predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsNEQ(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsIn applies the In predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIn(FieldDefaultWarrantyDetails, vs...))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsNotIn applies the NotIn predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsNotIn(vs ...string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotIn(FieldDefaultWarrantyDetails, vs...))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsGT applies the GT predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsGT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGT(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsGTE applies the GTE predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsGTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldGTE(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsLT applies the LT predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsLT(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLT(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsLTE applies the LTE predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsLTE(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldLTE(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsContains applies the Contains predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsContains(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContains(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsHasPrefix applies the HasPrefix predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsHasPrefix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasPrefix(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsHasSuffix applies the HasSuffix predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsHasSuffix(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldHasSuffix(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsIsNil applies the IsNil predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsIsNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldIsNull(FieldDefaultWarrantyDetails))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsNotNil applies the NotNil predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultWarrantyDetails))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsEqualFold applies the EqualFold predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsEqualFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEqualFold(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// DefaultWarrantyDetailsContainsFold applies the ContainsFold predicate on the "default_warranty_details" field.
|
||||
func DefaultWarrantyDetailsContainsFold(v string) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldContainsFold(FieldDefaultWarrantyDetails, v))
|
||||
}
|
||||
|
||||
// IncludeWarrantyFieldsEQ applies the EQ predicate on the "include_warranty_fields" field.
|
||||
func IncludeWarrantyFieldsEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldIncludeWarrantyFields, v))
|
||||
}
|
||||
|
||||
// IncludeWarrantyFieldsNEQ applies the NEQ predicate on the "include_warranty_fields" field.
|
||||
func IncludeWarrantyFieldsNEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldIncludeWarrantyFields, v))
|
||||
}
|
||||
|
||||
// IncludePurchaseFieldsEQ applies the EQ predicate on the "include_purchase_fields" field.
|
||||
func IncludePurchaseFieldsEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldIncludePurchaseFields, v))
|
||||
}
|
||||
|
||||
// IncludePurchaseFieldsNEQ applies the NEQ predicate on the "include_purchase_fields" field.
|
||||
func IncludePurchaseFieldsNEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNEQ(FieldIncludePurchaseFields, v))
|
||||
}
|
||||
|
||||
// IncludeSoldFieldsEQ applies the EQ predicate on the "include_sold_fields" field.
|
||||
func IncludeSoldFieldsEQ(v bool) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldEQ(FieldIncludeSoldFields, v))
|
||||
}
|
||||
|
||||
// IncludeSoldFieldsNEQ applies the NEQ predicate on the "include_sold_fields" field.
|
||||
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))
|
||||
}
|
||||
|
||||
// DefaultLabelIdsNotNil applies the NotNil predicate on the "default_label_ids" field.
|
||||
func DefaultLabelIdsNotNil() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.FieldNotNull(FieldDefaultLabelIds))
|
||||
}
|
||||
|
||||
// HasGroup applies the HasEdge predicate on the "group" edge.
|
||||
func HasGroup() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(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.ItemTemplate {
|
||||
return predicate.ItemTemplate(func(s *sql.Selector) {
|
||||
step := newGroupStep()
|
||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||
for _, p := range preds {
|
||||
p(s)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// HasFields applies the HasEdge predicate on the "fields" edge.
|
||||
func HasFields() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, FieldsTable, FieldsColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasFieldsWith applies the HasEdge predicate on the "fields" edge with a given conditions (other predicates).
|
||||
func HasFieldsWith(preds ...predicate.TemplateField) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(func(s *sql.Selector) {
|
||||
step := newFieldsStep()
|
||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||
for _, p := range preds {
|
||||
p(s)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// HasLocation applies the HasEdge predicate on the "location" edge.
|
||||
func HasLocation() predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, false, LocationTable, LocationColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasLocationWith applies the HasEdge predicate on the "location" edge with a given conditions (other predicates).
|
||||
func HasLocationWith(preds ...predicate.Location) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(func(s *sql.Selector) {
|
||||
step := newLocationStep()
|
||||
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.ItemTemplate) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.AndPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Or groups predicates with the OR operator between them.
|
||||
func Or(predicates ...predicate.ItemTemplate) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.OrPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Not applies the not operator on the given predicate.
|
||||
func Not(p predicate.ItemTemplate) predicate.ItemTemplate {
|
||||
return predicate.ItemTemplate(sql.NotPredicates(p))
|
||||
}
|
||||
691
backend/internal/data/ent/itemtemplate_create.go
generated
Normal file
@@ -0,0 +1,691 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/google/uuid"
|
||||
"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/location"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/templatefield"
|
||||
)
|
||||
|
||||
// ItemTemplateCreate is the builder for creating a ItemTemplate entity.
|
||||
type ItemTemplateCreate struct {
|
||||
config
|
||||
mutation *ItemTemplateMutation
|
||||
hooks []Hook
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the "created_at" field.
|
||||
func (_c *ItemTemplateCreate) SetCreatedAt(v time.Time) *ItemTemplateCreate {
|
||||
_c.mutation.SetCreatedAt(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableCreatedAt(v *time.Time) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetCreatedAt(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_c *ItemTemplateCreate) SetUpdatedAt(v time.Time) *ItemTemplateCreate {
|
||||
_c.mutation.SetUpdatedAt(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableUpdatedAt(v *time.Time) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetUpdatedAt(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetName sets the "name" field.
|
||||
func (_c *ItemTemplateCreate) SetName(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetName(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDescription sets the "description" field.
|
||||
func (_c *ItemTemplateCreate) SetDescription(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetDescription(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDescription sets the "description" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDescription(v *string) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDescription(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNotes sets the "notes" field.
|
||||
func (_c *ItemTemplateCreate) SetNotes(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetNotes(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableNotes sets the "notes" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableNotes(v *string) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetNotes(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultQuantity sets the "default_quantity" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultQuantity(v int) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultQuantity(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultQuantity sets the "default_quantity" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultQuantity(v *int) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultQuantity(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultInsured sets the "default_insured" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultInsured(v bool) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultInsured(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultInsured sets the "default_insured" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultInsured(v *bool) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultInsured(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultName sets the "default_name" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultName(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultName(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultName sets the "default_name" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultName(v *string) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultName(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultDescription sets the "default_description" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultDescription(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultDescription(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultDescription sets the "default_description" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultDescription(v *string) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultDescription(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultManufacturer sets the "default_manufacturer" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultManufacturer(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultManufacturer(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultManufacturer sets the "default_manufacturer" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultManufacturer(v *string) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultManufacturer(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultModelNumber sets the "default_model_number" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultModelNumber(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultModelNumber(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultModelNumber sets the "default_model_number" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultModelNumber(v *string) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultModelNumber(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultLifetimeWarranty sets the "default_lifetime_warranty" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultLifetimeWarranty(v bool) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultLifetimeWarranty(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultLifetimeWarranty sets the "default_lifetime_warranty" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultLifetimeWarranty(v *bool) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultLifetimeWarranty(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultWarrantyDetails sets the "default_warranty_details" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultWarrantyDetails(v string) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultWarrantyDetails(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultWarrantyDetails sets the "default_warranty_details" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableDefaultWarrantyDetails(v *string) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultWarrantyDetails(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetIncludeWarrantyFields sets the "include_warranty_fields" field.
|
||||
func (_c *ItemTemplateCreate) SetIncludeWarrantyFields(v bool) *ItemTemplateCreate {
|
||||
_c.mutation.SetIncludeWarrantyFields(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableIncludeWarrantyFields sets the "include_warranty_fields" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableIncludeWarrantyFields(v *bool) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetIncludeWarrantyFields(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetIncludePurchaseFields sets the "include_purchase_fields" field.
|
||||
func (_c *ItemTemplateCreate) SetIncludePurchaseFields(v bool) *ItemTemplateCreate {
|
||||
_c.mutation.SetIncludePurchaseFields(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableIncludePurchaseFields sets the "include_purchase_fields" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableIncludePurchaseFields(v *bool) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetIncludePurchaseFields(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetIncludeSoldFields sets the "include_sold_fields" field.
|
||||
func (_c *ItemTemplateCreate) SetIncludeSoldFields(v bool) *ItemTemplateCreate {
|
||||
_c.mutation.SetIncludeSoldFields(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableIncludeSoldFields sets the "include_sold_fields" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableIncludeSoldFields(v *bool) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetIncludeSoldFields(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultLabelIds sets the "default_label_ids" field.
|
||||
func (_c *ItemTemplateCreate) SetDefaultLabelIds(v []uuid.UUID) *ItemTemplateCreate {
|
||||
_c.mutation.SetDefaultLabelIds(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetID sets the "id" field.
|
||||
func (_c *ItemTemplateCreate) SetID(v uuid.UUID) *ItemTemplateCreate {
|
||||
_c.mutation.SetID(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableID sets the "id" field if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableID(v *uuid.UUID) *ItemTemplateCreate {
|
||||
if v != nil {
|
||||
_c.SetID(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetGroupID sets the "group" edge to the Group entity by ID.
|
||||
func (_c *ItemTemplateCreate) SetGroupID(id uuid.UUID) *ItemTemplateCreate {
|
||||
_c.mutation.SetGroupID(id)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetGroup sets the "group" edge to the Group entity.
|
||||
func (_c *ItemTemplateCreate) SetGroup(v *Group) *ItemTemplateCreate {
|
||||
return _c.SetGroupID(v.ID)
|
||||
}
|
||||
|
||||
// AddFieldIDs adds the "fields" edge to the TemplateField entity by IDs.
|
||||
func (_c *ItemTemplateCreate) AddFieldIDs(ids ...uuid.UUID) *ItemTemplateCreate {
|
||||
_c.mutation.AddFieldIDs(ids...)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddFields adds the "fields" edges to the TemplateField entity.
|
||||
func (_c *ItemTemplateCreate) AddFields(v ...*TemplateField) *ItemTemplateCreate {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _c.AddFieldIDs(ids...)
|
||||
}
|
||||
|
||||
// SetLocationID sets the "location" edge to the Location entity by ID.
|
||||
func (_c *ItemTemplateCreate) SetLocationID(id uuid.UUID) *ItemTemplateCreate {
|
||||
_c.mutation.SetLocationID(id)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableLocationID sets the "location" edge to the Location entity by ID if the given value is not nil.
|
||||
func (_c *ItemTemplateCreate) SetNillableLocationID(id *uuid.UUID) *ItemTemplateCreate {
|
||||
if id != nil {
|
||||
_c = _c.SetLocationID(*id)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetLocation sets the "location" edge to the Location entity.
|
||||
func (_c *ItemTemplateCreate) SetLocation(v *Location) *ItemTemplateCreate {
|
||||
return _c.SetLocationID(v.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the ItemTemplateMutation object of the builder.
|
||||
func (_c *ItemTemplateCreate) Mutation() *ItemTemplateMutation {
|
||||
return _c.mutation
|
||||
}
|
||||
|
||||
// Save creates the ItemTemplate in the database.
|
||||
func (_c *ItemTemplateCreate) Save(ctx context.Context) (*ItemTemplate, error) {
|
||||
_c.defaults()
|
||||
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
|
||||
}
|
||||
|
||||
// SaveX calls Save and panics if Save returns an error.
|
||||
func (_c *ItemTemplateCreate) SaveX(ctx context.Context) *ItemTemplate {
|
||||
v, err := _c.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_c *ItemTemplateCreate) Exec(ctx context.Context) error {
|
||||
_, err := _c.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_c *ItemTemplateCreate) 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 *ItemTemplateCreate) defaults() {
|
||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||
v := itemtemplate.DefaultCreatedAt()
|
||||
_c.mutation.SetCreatedAt(v)
|
||||
}
|
||||
if _, ok := _c.mutation.UpdatedAt(); !ok {
|
||||
v := itemtemplate.DefaultUpdatedAt()
|
||||
_c.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
if _, ok := _c.mutation.DefaultQuantity(); !ok {
|
||||
v := itemtemplate.DefaultDefaultQuantity
|
||||
_c.mutation.SetDefaultQuantity(v)
|
||||
}
|
||||
if _, ok := _c.mutation.DefaultInsured(); !ok {
|
||||
v := itemtemplate.DefaultDefaultInsured
|
||||
_c.mutation.SetDefaultInsured(v)
|
||||
}
|
||||
if _, ok := _c.mutation.DefaultLifetimeWarranty(); !ok {
|
||||
v := itemtemplate.DefaultDefaultLifetimeWarranty
|
||||
_c.mutation.SetDefaultLifetimeWarranty(v)
|
||||
}
|
||||
if _, ok := _c.mutation.IncludeWarrantyFields(); !ok {
|
||||
v := itemtemplate.DefaultIncludeWarrantyFields
|
||||
_c.mutation.SetIncludeWarrantyFields(v)
|
||||
}
|
||||
if _, ok := _c.mutation.IncludePurchaseFields(); !ok {
|
||||
v := itemtemplate.DefaultIncludePurchaseFields
|
||||
_c.mutation.SetIncludePurchaseFields(v)
|
||||
}
|
||||
if _, ok := _c.mutation.IncludeSoldFields(); !ok {
|
||||
v := itemtemplate.DefaultIncludeSoldFields
|
||||
_c.mutation.SetIncludeSoldFields(v)
|
||||
}
|
||||
if _, ok := _c.mutation.ID(); !ok {
|
||||
v := itemtemplate.DefaultID()
|
||||
_c.mutation.SetID(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_c *ItemTemplateCreate) check() error {
|
||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "ItemTemplate.created_at"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.UpdatedAt(); !ok {
|
||||
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "ItemTemplate.updated_at"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.Name(); !ok {
|
||||
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "ItemTemplate.name"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.Name(); ok {
|
||||
if err := itemtemplate.NameValidator(v); err != nil {
|
||||
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _c.mutation.Description(); ok {
|
||||
if err := itemtemplate.DescriptionValidator(v); err != nil {
|
||||
return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.description": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _c.mutation.Notes(); ok {
|
||||
if err := itemtemplate.NotesValidator(v); err != nil {
|
||||
return &ValidationError{Name: "notes", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.notes": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.DefaultQuantity(); !ok {
|
||||
return &ValidationError{Name: "default_quantity", err: errors.New(`ent: missing required field "ItemTemplate.default_quantity"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.DefaultInsured(); !ok {
|
||||
return &ValidationError{Name: "default_insured", err: errors.New(`ent: missing required field "ItemTemplate.default_insured"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.DefaultName(); ok {
|
||||
if err := itemtemplate.DefaultNameValidator(v); err != nil {
|
||||
return &ValidationError{Name: "default_name", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.default_name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _c.mutation.DefaultDescription(); ok {
|
||||
if err := itemtemplate.DefaultDescriptionValidator(v); err != nil {
|
||||
return &ValidationError{Name: "default_description", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.default_description": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _c.mutation.DefaultManufacturer(); ok {
|
||||
if err := itemtemplate.DefaultManufacturerValidator(v); err != nil {
|
||||
return &ValidationError{Name: "default_manufacturer", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.default_manufacturer": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _c.mutation.DefaultModelNumber(); ok {
|
||||
if err := itemtemplate.DefaultModelNumberValidator(v); err != nil {
|
||||
return &ValidationError{Name: "default_model_number", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.default_model_number": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.DefaultLifetimeWarranty(); !ok {
|
||||
return &ValidationError{Name: "default_lifetime_warranty", err: errors.New(`ent: missing required field "ItemTemplate.default_lifetime_warranty"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.DefaultWarrantyDetails(); ok {
|
||||
if err := itemtemplate.DefaultWarrantyDetailsValidator(v); err != nil {
|
||||
return &ValidationError{Name: "default_warranty_details", err: fmt.Errorf(`ent: validator failed for field "ItemTemplate.default_warranty_details": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.IncludeWarrantyFields(); !ok {
|
||||
return &ValidationError{Name: "include_warranty_fields", err: errors.New(`ent: missing required field "ItemTemplate.include_warranty_fields"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.IncludePurchaseFields(); !ok {
|
||||
return &ValidationError{Name: "include_purchase_fields", err: errors.New(`ent: missing required field "ItemTemplate.include_purchase_fields"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.IncludeSoldFields(); !ok {
|
||||
return &ValidationError{Name: "include_sold_fields", err: errors.New(`ent: missing required field "ItemTemplate.include_sold_fields"`)}
|
||||
}
|
||||
if len(_c.mutation.GroupIDs()) == 0 {
|
||||
return &ValidationError{Name: "group", err: errors.New(`ent: missing required edge "ItemTemplate.group"`)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_c *ItemTemplateCreate) sqlSave(ctx context.Context) (*ItemTemplate, error) {
|
||||
if err := _c.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_node, _spec := _c.createSpec()
|
||||
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
|
||||
if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if _spec.ID.Value != nil {
|
||||
if id, ok := _spec.ID.Value.(*uuid.UUID); ok {
|
||||
_node.ID = *id
|
||||
} else if err := _node.ID.Scan(_spec.ID.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_c.mutation.id = &_node.ID
|
||||
_c.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
|
||||
func (_c *ItemTemplateCreate) createSpec() (*ItemTemplate, *sqlgraph.CreateSpec) {
|
||||
var (
|
||||
_node = &ItemTemplate{config: _c.config}
|
||||
_spec = sqlgraph.NewCreateSpec(itemtemplate.Table, sqlgraph.NewFieldSpec(itemtemplate.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(itemtemplate.FieldCreatedAt, field.TypeTime, value)
|
||||
_node.CreatedAt = value
|
||||
}
|
||||
if value, ok := _c.mutation.UpdatedAt(); ok {
|
||||
_spec.SetField(itemtemplate.FieldUpdatedAt, field.TypeTime, value)
|
||||
_node.UpdatedAt = value
|
||||
}
|
||||
if value, ok := _c.mutation.Name(); ok {
|
||||
_spec.SetField(itemtemplate.FieldName, field.TypeString, value)
|
||||
_node.Name = value
|
||||
}
|
||||
if value, ok := _c.mutation.Description(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDescription, field.TypeString, value)
|
||||
_node.Description = value
|
||||
}
|
||||
if value, ok := _c.mutation.Notes(); ok {
|
||||
_spec.SetField(itemtemplate.FieldNotes, field.TypeString, value)
|
||||
_node.Notes = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultQuantity(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultQuantity, field.TypeInt, value)
|
||||
_node.DefaultQuantity = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultInsured(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultInsured, field.TypeBool, value)
|
||||
_node.DefaultInsured = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultName(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultName, field.TypeString, value)
|
||||
_node.DefaultName = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultDescription(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultDescription, field.TypeString, value)
|
||||
_node.DefaultDescription = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultManufacturer(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultManufacturer, field.TypeString, value)
|
||||
_node.DefaultManufacturer = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultModelNumber(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultModelNumber, field.TypeString, value)
|
||||
_node.DefaultModelNumber = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultLifetimeWarranty(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultLifetimeWarranty, field.TypeBool, value)
|
||||
_node.DefaultLifetimeWarranty = value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultWarrantyDetails(); ok {
|
||||
_spec.SetField(itemtemplate.FieldDefaultWarrantyDetails, field.TypeString, value)
|
||||
_node.DefaultWarrantyDetails = value
|
||||
}
|
||||
if value, ok := _c.mutation.IncludeWarrantyFields(); ok {
|
||||
_spec.SetField(itemtemplate.FieldIncludeWarrantyFields, field.TypeBool, value)
|
||||
_node.IncludeWarrantyFields = value
|
||||
}
|
||||
if value, ok := _c.mutation.IncludePurchaseFields(); ok {
|
||||
_spec.SetField(itemtemplate.FieldIncludePurchaseFields, field.TypeBool, value)
|
||||
_node.IncludePurchaseFields = value
|
||||
}
|
||||
if value, ok := _c.mutation.IncludeSoldFields(); ok {
|
||||
_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 nodes := _c.mutation.GroupIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: itemtemplate.GroupTable,
|
||||
Columns: []string{itemtemplate.GroupColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_node.group_item_templates = &nodes[0]
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
if nodes := _c.mutation.FieldsIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: itemtemplate.FieldsTable,
|
||||
Columns: []string{itemtemplate.FieldsColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(templatefield.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
if nodes := _c.mutation.LocationIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: false,
|
||||
Table: itemtemplate.LocationTable,
|
||||
Columns: []string{itemtemplate.LocationColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_node.item_template_location = &nodes[0]
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
return _node, _spec
|
||||
}
|
||||
|
||||
// ItemTemplateCreateBulk is the builder for creating many ItemTemplate entities in bulk.
|
||||
type ItemTemplateCreateBulk struct {
|
||||
config
|
||||
err error
|
||||
builders []*ItemTemplateCreate
|
||||
}
|
||||
|
||||
// Save creates the ItemTemplate entities in the database.
|
||||
func (_c *ItemTemplateCreateBulk) Save(ctx context.Context) ([]*ItemTemplate, error) {
|
||||
if _c.err != nil {
|
||||
return nil, _c.err
|
||||
}
|
||||
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
|
||||
nodes := make([]*ItemTemplate, 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.(*ItemTemplateMutation)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||
}
|
||||
if err := builder.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder.mutation = mutation
|
||||
var err error
|
||||
nodes[i], specs[i] = builder.createSpec()
|
||||
if i < len(mutators)-1 {
|
||||
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
|
||||
} else {
|
||||
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
||||
// Invoke the actual operation on the latest mutation in the chain.
|
||||
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
|
||||
if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mutation.id = &nodes[i].ID
|
||||
mutation.done = true
|
||||
return nodes[i], nil
|
||||
})
|
||||
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
||||
mut = builder.hooks[i](mut)
|
||||
}
|
||||
mutators[i] = mut
|
||||
}(i, ctx)
|
||||
}
|
||||
if len(mutators) > 0 {
|
||||
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_c *ItemTemplateCreateBulk) SaveX(ctx context.Context) []*ItemTemplate {
|
||||
v, err := _c.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_c *ItemTemplateCreateBulk) Exec(ctx context.Context) error {
|
||||
_, err := _c.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_c *ItemTemplateCreateBulk) ExecX(ctx context.Context) {
|
||||
if err := _c.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
88
backend/internal/data/ent/itemtemplate_delete.go
generated
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
|
||||
)
|
||||
|
||||
// ItemTemplateDelete is the builder for deleting a ItemTemplate entity.
|
||||
type ItemTemplateDelete struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *ItemTemplateMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ItemTemplateDelete builder.
|
||||
func (_d *ItemTemplateDelete) Where(ps ...predicate.ItemTemplate) *ItemTemplateDelete {
|
||||
_d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||
func (_d *ItemTemplateDelete) 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 *ItemTemplateDelete) ExecX(ctx context.Context) int {
|
||||
n, err := _d.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (_d *ItemTemplateDelete) sqlExec(ctx context.Context) (int, error) {
|
||||
_spec := sqlgraph.NewDeleteSpec(itemtemplate.Table, sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID))
|
||||
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
_d.mutation.done = true
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// ItemTemplateDeleteOne is the builder for deleting a single ItemTemplate entity.
|
||||
type ItemTemplateDeleteOne struct {
|
||||
_d *ItemTemplateDelete
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ItemTemplateDelete builder.
|
||||
func (_d *ItemTemplateDeleteOne) Where(ps ...predicate.ItemTemplate) *ItemTemplateDeleteOne {
|
||||
_d._d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query.
|
||||
func (_d *ItemTemplateDeleteOne) Exec(ctx context.Context) error {
|
||||
n, err := _d._d.Exec(ctx)
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case n == 0:
|
||||
return &NotFoundError{itemtemplate.Label}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *ItemTemplateDeleteOne) ExecX(ctx context.Context) {
|
||||
if err := _d.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
766
backend/internal/data/ent/itemtemplate_query.go
generated
Normal file
@@ -0,0 +1,766 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/google/uuid"
|
||||
"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/location"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/templatefield"
|
||||
)
|
||||
|
||||
// ItemTemplateQuery is the builder for querying ItemTemplate entities.
|
||||
type ItemTemplateQuery struct {
|
||||
config
|
||||
ctx *QueryContext
|
||||
order []itemtemplate.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.ItemTemplate
|
||||
withGroup *GroupQuery
|
||||
withFields *TemplateFieldQuery
|
||||
withLocation *LocationQuery
|
||||
withFKs bool
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
}
|
||||
|
||||
// Where adds a new predicate for the ItemTemplateQuery builder.
|
||||
func (_q *ItemTemplateQuery) Where(ps ...predicate.ItemTemplate) *ItemTemplateQuery {
|
||||
_q.predicates = append(_q.predicates, ps...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// Limit the number of records to be returned by this query.
|
||||
func (_q *ItemTemplateQuery) Limit(limit int) *ItemTemplateQuery {
|
||||
_q.ctx.Limit = &limit
|
||||
return _q
|
||||
}
|
||||
|
||||
// Offset to start from.
|
||||
func (_q *ItemTemplateQuery) Offset(offset int) *ItemTemplateQuery {
|
||||
_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 *ItemTemplateQuery) Unique(unique bool) *ItemTemplateQuery {
|
||||
_q.ctx.Unique = &unique
|
||||
return _q
|
||||
}
|
||||
|
||||
// Order specifies how the records should be ordered.
|
||||
func (_q *ItemTemplateQuery) Order(o ...itemtemplate.OrderOption) *ItemTemplateQuery {
|
||||
_q.order = append(_q.order, o...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// QueryGroup chains the current query on the "group" edge.
|
||||
func (_q *ItemTemplateQuery) 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 {
|
||||
return nil, err
|
||||
}
|
||||
selector := _q.sqlQuery(ctx)
|
||||
if err := selector.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(itemtemplate.Table, itemtemplate.FieldID, selector),
|
||||
sqlgraph.To(group.Table, group.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, itemtemplate.GroupTable, itemtemplate.GroupColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryFields chains the current query on the "fields" edge.
|
||||
func (_q *ItemTemplateQuery) QueryFields() *TemplateFieldQuery {
|
||||
query := (&TemplateFieldClient{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
|
||||
}
|
||||
selector := _q.sqlQuery(ctx)
|
||||
if err := selector.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(itemtemplate.Table, itemtemplate.FieldID, selector),
|
||||
sqlgraph.To(templatefield.Table, templatefield.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, itemtemplate.FieldsTable, itemtemplate.FieldsColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryLocation chains the current query on the "location" edge.
|
||||
func (_q *ItemTemplateQuery) QueryLocation() *LocationQuery {
|
||||
query := (&LocationClient{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
|
||||
}
|
||||
selector := _q.sqlQuery(ctx)
|
||||
if err := selector.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(itemtemplate.Table, itemtemplate.FieldID, selector),
|
||||
sqlgraph.To(location.Table, location.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, false, itemtemplate.LocationTable, itemtemplate.LocationColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// First returns the first ItemTemplate entity from the query.
|
||||
// Returns a *NotFoundError when no ItemTemplate was found.
|
||||
func (_q *ItemTemplateQuery) First(ctx context.Context) (*ItemTemplate, 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{itemtemplate.Label}
|
||||
}
|
||||
return nodes[0], nil
|
||||
}
|
||||
|
||||
// FirstX is like First, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) FirstX(ctx context.Context) *ItemTemplate {
|
||||
node, err := _q.First(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// FirstID returns the first ItemTemplate ID from the query.
|
||||
// Returns a *NotFoundError when no ItemTemplate ID was found.
|
||||
func (_q *ItemTemplateQuery) 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{itemtemplate.Label}
|
||||
return
|
||||
}
|
||||
return ids[0], nil
|
||||
}
|
||||
|
||||
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) FirstIDX(ctx context.Context) uuid.UUID {
|
||||
id, err := _q.FirstID(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Only returns a single ItemTemplate entity found by the query, ensuring it only returns one.
|
||||
// Returns a *NotSingularError when more than one ItemTemplate entity is found.
|
||||
// Returns a *NotFoundError when no ItemTemplate entities are found.
|
||||
func (_q *ItemTemplateQuery) Only(ctx context.Context) (*ItemTemplate, error) {
|
||||
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch len(nodes) {
|
||||
case 1:
|
||||
return nodes[0], nil
|
||||
case 0:
|
||||
return nil, &NotFoundError{itemtemplate.Label}
|
||||
default:
|
||||
return nil, &NotSingularError{itemtemplate.Label}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyX is like Only, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) OnlyX(ctx context.Context) *ItemTemplate {
|
||||
node, err := _q.Only(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// OnlyID is like Only, but returns the only ItemTemplate ID in the query.
|
||||
// Returns a *NotSingularError when more than one ItemTemplate ID is found.
|
||||
// Returns a *NotFoundError when no entities are found.
|
||||
func (_q *ItemTemplateQuery) 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
|
||||
}
|
||||
switch len(ids) {
|
||||
case 1:
|
||||
id = ids[0]
|
||||
case 0:
|
||||
err = &NotFoundError{itemtemplate.Label}
|
||||
default:
|
||||
err = &NotSingularError{itemtemplate.Label}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) OnlyIDX(ctx context.Context) uuid.UUID {
|
||||
id, err := _q.OnlyID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// All executes the query and returns a list of ItemTemplates.
|
||||
func (_q *ItemTemplateQuery) All(ctx context.Context) ([]*ItemTemplate, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qr := querierAll[[]*ItemTemplate, *ItemTemplateQuery]()
|
||||
return withInterceptors[[]*ItemTemplate](ctx, _q, qr, _q.inters)
|
||||
}
|
||||
|
||||
// AllX is like All, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) AllX(ctx context.Context) []*ItemTemplate {
|
||||
nodes, err := _q.All(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// IDs executes the query and returns a list of ItemTemplate IDs.
|
||||
func (_q *ItemTemplateQuery) 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(itemtemplate.FieldID).Scan(ctx, &ids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IDsX is like IDs, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) IDsX(ctx context.Context) []uuid.UUID {
|
||||
ids, err := _q.IDs(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// Count returns the count of the given query.
|
||||
func (_q *ItemTemplateQuery) 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[*ItemTemplateQuery](), _q.inters)
|
||||
}
|
||||
|
||||
// CountX is like Count, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) CountX(ctx context.Context) int {
|
||||
count, err := _q.Count(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Exist returns true if the query has elements in the graph.
|
||||
func (_q *ItemTemplateQuery) Exist(ctx context.Context) (bool, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
|
||||
switch _, err := _q.FirstID(ctx); {
|
||||
case IsNotFound(err):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, fmt.Errorf("ent: check existence: %w", err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExistX is like Exist, but panics if an error occurs.
|
||||
func (_q *ItemTemplateQuery) ExistX(ctx context.Context) bool {
|
||||
exist, err := _q.Exist(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exist
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the ItemTemplateQuery 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 *ItemTemplateQuery) Clone() *ItemTemplateQuery {
|
||||
if _q == nil {
|
||||
return nil
|
||||
}
|
||||
return &ItemTemplateQuery{
|
||||
config: _q.config,
|
||||
ctx: _q.ctx.Clone(),
|
||||
order: append([]itemtemplate.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.ItemTemplate{}, _q.predicates...),
|
||||
withGroup: _q.withGroup.Clone(),
|
||||
withFields: _q.withFields.Clone(),
|
||||
withLocation: _q.withLocation.Clone(),
|
||||
// clone intermediate query.
|
||||
sql: _q.sql.Clone(),
|
||||
path: _q.path,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *ItemTemplateQuery) WithGroup(opts ...func(*GroupQuery)) *ItemTemplateQuery {
|
||||
query := (&GroupClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withGroup = query
|
||||
return _q
|
||||
}
|
||||
|
||||
// WithFields tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "fields" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *ItemTemplateQuery) WithFields(opts ...func(*TemplateFieldQuery)) *ItemTemplateQuery {
|
||||
query := (&TemplateFieldClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withFields = query
|
||||
return _q
|
||||
}
|
||||
|
||||
// WithLocation tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "location" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *ItemTemplateQuery) WithLocation(opts ...func(*LocationQuery)) *ItemTemplateQuery {
|
||||
query := (&LocationClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withLocation = query
|
||||
return _q
|
||||
}
|
||||
|
||||
// GroupBy is used to group vertices by one or more fields/columns.
|
||||
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// Count int `json:"count,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.ItemTemplate.Query().
|
||||
// GroupBy(itemtemplate.FieldCreatedAt).
|
||||
// Aggregate(ent.Count()).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *ItemTemplateQuery) GroupBy(field string, fields ...string) *ItemTemplateGroupBy {
|
||||
_q.ctx.Fields = append([]string{field}, fields...)
|
||||
grbuild := &ItemTemplateGroupBy{build: _q}
|
||||
grbuild.flds = &_q.ctx.Fields
|
||||
grbuild.label = itemtemplate.Label
|
||||
grbuild.scan = grbuild.Scan
|
||||
return grbuild
|
||||
}
|
||||
|
||||
// Select allows the selection one or more fields/columns for the given query,
|
||||
// instead of selecting all fields in the entity.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.ItemTemplate.Query().
|
||||
// Select(itemtemplate.FieldCreatedAt).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *ItemTemplateQuery) Select(fields ...string) *ItemTemplateSelect {
|
||||
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||
sbuild := &ItemTemplateSelect{ItemTemplateQuery: _q}
|
||||
sbuild.label = itemtemplate.Label
|
||||
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||
return sbuild
|
||||
}
|
||||
|
||||
// Aggregate returns a ItemTemplateSelect configured with the given aggregations.
|
||||
func (_q *ItemTemplateQuery) Aggregate(fns ...AggregateFunc) *ItemTemplateSelect {
|
||||
return _q.Select().Aggregate(fns...)
|
||||
}
|
||||
|
||||
func (_q *ItemTemplateQuery) prepareQuery(ctx context.Context) error {
|
||||
for _, inter := range _q.inters {
|
||||
if inter == nil {
|
||||
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
|
||||
}
|
||||
if trv, ok := inter.(Traverser); ok {
|
||||
if err := trv.Traverse(ctx, _q); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range _q.ctx.Fields {
|
||||
if !itemtemplate.ValidColumn(f) {
|
||||
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
}
|
||||
if _q.path != nil {
|
||||
prev, err := _q.path(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_q.sql = prev
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *ItemTemplateQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ItemTemplate, error) {
|
||||
var (
|
||||
nodes = []*ItemTemplate{}
|
||||
withFKs = _q.withFKs
|
||||
_spec = _q.querySpec()
|
||||
loadedTypes = [3]bool{
|
||||
_q.withGroup != nil,
|
||||
_q.withFields != nil,
|
||||
_q.withLocation != nil,
|
||||
}
|
||||
)
|
||||
if _q.withGroup != nil || _q.withLocation != nil {
|
||||
withFKs = true
|
||||
}
|
||||
if withFKs {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, itemtemplate.ForeignKeys...)
|
||||
}
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
return (*ItemTemplate).scanValues(nil, columns)
|
||||
}
|
||||
_spec.Assign = func(columns []string, values []any) error {
|
||||
node := &ItemTemplate{config: _q.config}
|
||||
nodes = append(nodes, node)
|
||||
node.Edges.loadedTypes = loadedTypes
|
||||
return node.assignValues(columns, values)
|
||||
}
|
||||
for i := range hooks {
|
||||
hooks[i](ctx, _spec)
|
||||
}
|
||||
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
if query := _q.withGroup; query != nil {
|
||||
if err := _q.loadGroup(ctx, query, nodes, nil,
|
||||
func(n *ItemTemplate, e *Group) { n.Edges.Group = e }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if query := _q.withFields; query != nil {
|
||||
if err := _q.loadFields(ctx, query, nodes,
|
||||
func(n *ItemTemplate) { n.Edges.Fields = []*TemplateField{} },
|
||||
func(n *ItemTemplate, e *TemplateField) { n.Edges.Fields = append(n.Edges.Fields, e) }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if query := _q.withLocation; query != nil {
|
||||
if err := _q.loadLocation(ctx, query, nodes, nil,
|
||||
func(n *ItemTemplate, e *Location) { n.Edges.Location = e }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (_q *ItemTemplateQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*ItemTemplate, init func(*ItemTemplate), assign func(*ItemTemplate, *Group)) error {
|
||||
ids := make([]uuid.UUID, 0, len(nodes))
|
||||
nodeids := make(map[uuid.UUID][]*ItemTemplate)
|
||||
for i := range nodes {
|
||||
if nodes[i].group_item_templates == nil {
|
||||
continue
|
||||
}
|
||||
fk := *nodes[i].group_item_templates
|
||||
if _, ok := nodeids[fk]; !ok {
|
||||
ids = append(ids, fk)
|
||||
}
|
||||
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
query.Where(group.IDIn(ids...))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
nodes, ok := nodeids[n.ID]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected foreign-key "group_item_templates" returned %v`, n.ID)
|
||||
}
|
||||
for i := range nodes {
|
||||
assign(nodes[i], n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (_q *ItemTemplateQuery) loadFields(ctx context.Context, query *TemplateFieldQuery, nodes []*ItemTemplate, init func(*ItemTemplate), assign func(*ItemTemplate, *TemplateField)) error {
|
||||
fks := make([]driver.Value, 0, len(nodes))
|
||||
nodeids := make(map[uuid.UUID]*ItemTemplate)
|
||||
for i := range nodes {
|
||||
fks = append(fks, nodes[i].ID)
|
||||
nodeids[nodes[i].ID] = nodes[i]
|
||||
if init != nil {
|
||||
init(nodes[i])
|
||||
}
|
||||
}
|
||||
query.withFKs = true
|
||||
query.Where(predicate.TemplateField(func(s *sql.Selector) {
|
||||
s.Where(sql.InValues(s.C(itemtemplate.FieldsColumn), fks...))
|
||||
}))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
fk := n.item_template_fields
|
||||
if fk == nil {
|
||||
return fmt.Errorf(`foreign-key "item_template_fields" is nil for node %v`, n.ID)
|
||||
}
|
||||
node, ok := nodeids[*fk]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected referenced foreign-key "item_template_fields" returned %v for node %v`, *fk, n.ID)
|
||||
}
|
||||
assign(node, n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (_q *ItemTemplateQuery) loadLocation(ctx context.Context, query *LocationQuery, nodes []*ItemTemplate, init func(*ItemTemplate), assign func(*ItemTemplate, *Location)) error {
|
||||
ids := make([]uuid.UUID, 0, len(nodes))
|
||||
nodeids := make(map[uuid.UUID][]*ItemTemplate)
|
||||
for i := range nodes {
|
||||
if nodes[i].item_template_location == nil {
|
||||
continue
|
||||
}
|
||||
fk := *nodes[i].item_template_location
|
||||
if _, ok := nodeids[fk]; !ok {
|
||||
ids = append(ids, fk)
|
||||
}
|
||||
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
query.Where(location.IDIn(ids...))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
nodes, ok := nodeids[n.ID]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected foreign-key "item_template_location" returned %v`, n.ID)
|
||||
}
|
||||
for i := range nodes {
|
||||
assign(nodes[i], n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *ItemTemplateQuery) sqlCount(ctx context.Context) (int, error) {
|
||||
_spec := _q.querySpec()
|
||||
_spec.Node.Columns = _q.ctx.Fields
|
||||
if len(_q.ctx.Fields) > 0 {
|
||||
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
|
||||
}
|
||||
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
|
||||
}
|
||||
|
||||
func (_q *ItemTemplateQuery) querySpec() *sqlgraph.QuerySpec {
|
||||
_spec := sqlgraph.NewQuerySpec(itemtemplate.Table, itemtemplate.Columns, sqlgraph.NewFieldSpec(itemtemplate.FieldID, field.TypeUUID))
|
||||
_spec.From = _q.sql
|
||||
if unique := _q.ctx.Unique; unique != nil {
|
||||
_spec.Unique = *unique
|
||||
} else if _q.path != nil {
|
||||
_spec.Unique = true
|
||||
}
|
||||
if fields := _q.ctx.Fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, itemtemplate.FieldID)
|
||||
for i := range fields {
|
||||
if fields[i] != itemtemplate.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := _q.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
_spec.Limit = *limit
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
_spec.Offset = *offset
|
||||
}
|
||||
if ps := _q.order; len(ps) > 0 {
|
||||
_spec.Order = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
return _spec
|
||||
}
|
||||
|
||||
func (_q *ItemTemplateQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||
builder := sql.Dialect(_q.driver.Dialect())
|
||||
t1 := builder.Table(itemtemplate.Table)
|
||||
columns := _q.ctx.Fields
|
||||
if len(columns) == 0 {
|
||||
columns = itemtemplate.Columns
|
||||
}
|
||||
selector := builder.Select(t1.Columns(columns...)...).From(t1)
|
||||
if _q.sql != nil {
|
||||
selector = _q.sql
|
||||
selector.Select(selector.Columns(columns...)...)
|
||||
}
|
||||
if _q.ctx.Unique != nil && *_q.ctx.Unique {
|
||||
selector.Distinct()
|
||||
}
|
||||
for _, p := range _q.predicates {
|
||||
p(selector)
|
||||
}
|
||||
for _, p := range _q.order {
|
||||
p(selector)
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
// limit is mandatory for offset clause. We start
|
||||
// with default value, and override it below if needed.
|
||||
selector.Offset(*offset).Limit(math.MaxInt32)
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
selector.Limit(*limit)
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
// ItemTemplateGroupBy is the group-by builder for ItemTemplate entities.
|
||||
type ItemTemplateGroupBy struct {
|
||||
selector
|
||||
build *ItemTemplateQuery
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the group-by query.
|
||||
func (_g *ItemTemplateGroupBy) Aggregate(fns ...AggregateFunc) *ItemTemplateGroupBy {
|
||||
_g.fns = append(_g.fns, fns...)
|
||||
return _g
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_g *ItemTemplateGroupBy) 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[*ItemTemplateQuery, *ItemTemplateGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||
}
|
||||
|
||||
func (_g *ItemTemplateGroupBy) sqlScan(ctx context.Context, root *ItemTemplateQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx).Select()
|
||||
aggregation := make([]string, 0, len(_g.fns))
|
||||
for _, fn := range _g.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
if len(selector.SelectedColumns()) == 0 {
|
||||
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
|
||||
for _, f := range *_g.flds {
|
||||
columns = append(columns, selector.C(f))
|
||||
}
|
||||
columns = append(columns, aggregation...)
|
||||
selector.Select(columns...)
|
||||
}
|
||||
selector.GroupBy(selector.Columns(*_g.flds...)...)
|
||||
if err := selector.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
|
||||
// ItemTemplateSelect is the builder for selecting fields of ItemTemplate entities.
|
||||
type ItemTemplateSelect struct {
|
||||
*ItemTemplateQuery
|
||||
selector
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the selector query.
|
||||
func (_s *ItemTemplateSelect) Aggregate(fns ...AggregateFunc) *ItemTemplateSelect {
|
||||
_s.fns = append(_s.fns, fns...)
|
||||
return _s
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_s *ItemTemplateSelect) 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[*ItemTemplateQuery, *ItemTemplateSelect](ctx, _s.ItemTemplateQuery, _s, _s.inters, v)
|
||||
}
|
||||
|
||||
func (_s *ItemTemplateSelect) sqlScan(ctx context.Context, root *ItemTemplateQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx)
|
||||
aggregation := make([]string, 0, len(_s.fns))
|
||||
for _, fn := range _s.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
switch n := len(*_s.selector.flds); {
|
||||
case n == 0 && len(aggregation) > 0:
|
||||
selector.Select(aggregation...)
|
||||
case n != 0 && len(aggregation) > 0:
|
||||
selector.AppendSelect(aggregation...)
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
1361
backend/internal/data/ent/itemtemplate_update.go
generated
Normal file
94
backend/internal/data/ent/migrate/schema.go
generated
@@ -246,6 +246,56 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
// ItemTemplatesColumns holds the columns for the "item_templates" table.
|
||||
ItemTemplatesColumns = []*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: "notes", Type: field.TypeString, Nullable: true, Size: 1000},
|
||||
{Name: "default_quantity", Type: field.TypeInt, Default: 1},
|
||||
{Name: "default_insured", Type: field.TypeBool, Default: false},
|
||||
{Name: "default_name", Type: field.TypeString, Nullable: true, Size: 255},
|
||||
{Name: "default_description", Type: field.TypeString, Nullable: true, Size: 1000},
|
||||
{Name: "default_manufacturer", Type: field.TypeString, Nullable: true, Size: 255},
|
||||
{Name: "default_model_number", Type: field.TypeString, Nullable: true, Size: 255},
|
||||
{Name: "default_lifetime_warranty", Type: field.TypeBool, Default: false},
|
||||
{Name: "default_warranty_details", Type: field.TypeString, Nullable: true, Size: 1000},
|
||||
{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: "group_item_templates", Type: field.TypeUUID},
|
||||
{Name: "item_template_location", Type: field.TypeUUID, Nullable: true},
|
||||
}
|
||||
// ItemTemplatesTable holds the schema information for the "item_templates" table.
|
||||
ItemTemplatesTable = &schema.Table{
|
||||
Name: "item_templates",
|
||||
Columns: ItemTemplatesColumns,
|
||||
PrimaryKey: []*schema.Column{ItemTemplatesColumns[0]},
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "item_templates_groups_item_templates",
|
||||
Columns: []*schema.Column{ItemTemplatesColumns[18]},
|
||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
{
|
||||
Symbol: "item_templates_locations_location",
|
||||
Columns: []*schema.Column{ItemTemplatesColumns[19]},
|
||||
RefColumns: []*schema.Column{LocationsColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "itemtemplate_name",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ItemTemplatesColumns[3]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// LabelsColumns holds the columns for the "labels" table.
|
||||
LabelsColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeUUID},
|
||||
@@ -379,6 +429,31 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
// TemplateFieldsColumns holds the columns for the "template_fields" table.
|
||||
TemplateFieldsColumns = []*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: "type", Type: field.TypeEnum, Enums: []string{"text"}},
|
||||
{Name: "text_value", Type: field.TypeString, Nullable: true, Size: 500},
|
||||
{Name: "item_template_fields", Type: field.TypeUUID, Nullable: true},
|
||||
}
|
||||
// TemplateFieldsTable holds the schema information for the "template_fields" table.
|
||||
TemplateFieldsTable = &schema.Table{
|
||||
Name: "template_fields",
|
||||
Columns: TemplateFieldsColumns,
|
||||
PrimaryKey: []*schema.Column{TemplateFieldsColumns[0]},
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "template_fields_item_templates_fields",
|
||||
Columns: []*schema.Column{TemplateFieldsColumns[7]},
|
||||
RefColumns: []*schema.Column{ItemTemplatesColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
},
|
||||
}
|
||||
// UsersColumns holds the columns for the "users" table.
|
||||
UsersColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeUUID},
|
||||
@@ -386,12 +461,13 @@ var (
|
||||
{Name: "updated_at", Type: field.TypeTime},
|
||||
{Name: "name", Type: field.TypeString, Size: 255},
|
||||
{Name: "email", Type: field.TypeString, Unique: true, Size: 255},
|
||||
{Name: "password", Type: field.TypeString, Size: 255},
|
||||
{Name: "password", Type: field.TypeString, Nullable: true, Size: 255},
|
||||
{Name: "is_superuser", Type: field.TypeBool, Default: false},
|
||||
{Name: "superuser", Type: field.TypeBool, Default: false},
|
||||
{Name: "role", Type: field.TypeEnum, Enums: []string{"user", "owner"}, Default: "user"},
|
||||
{Name: "activated_on", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "settings", Type: field.TypeJSON, Nullable: true},
|
||||
{Name: "oidc_issuer", Type: field.TypeString, Nullable: true},
|
||||
{Name: "oidc_subject", Type: field.TypeString, Nullable: true},
|
||||
{Name: "group_users", Type: field.TypeUUID},
|
||||
}
|
||||
// UsersTable holds the schema information for the "users" table.
|
||||
@@ -402,11 +478,18 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "users_groups_users",
|
||||
Columns: []*schema.Column{UsersColumns[11]},
|
||||
Columns: []*schema.Column{UsersColumns[12]},
|
||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "user_oidc_issuer_oidc_subject",
|
||||
Unique: true,
|
||||
Columns: []*schema.Column{UsersColumns[10], UsersColumns[11]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// LabelItemsColumns holds the columns for the "label_items" table.
|
||||
LabelItemsColumns = []*schema.Column{
|
||||
@@ -442,10 +525,12 @@ var (
|
||||
GroupInvitationTokensTable,
|
||||
ItemsTable,
|
||||
ItemFieldsTable,
|
||||
ItemTemplatesTable,
|
||||
LabelsTable,
|
||||
LocationsTable,
|
||||
MaintenanceEntriesTable,
|
||||
NotifiersTable,
|
||||
TemplateFieldsTable,
|
||||
UsersTable,
|
||||
LabelItemsTable,
|
||||
}
|
||||
@@ -461,12 +546,15 @@ func init() {
|
||||
ItemsTable.ForeignKeys[1].RefTable = ItemsTable
|
||||
ItemsTable.ForeignKeys[2].RefTable = LocationsTable
|
||||
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
|
||||
TemplateFieldsTable.ForeignKeys[0].RefTable = ItemTemplatesTable
|
||||
UsersTable.ForeignKeys[0].RefTable = GroupsTable
|
||||
LabelItemsTable.ForeignKeys[0].RefTable = LabelsTable
|
||||
LabelItemsTable.ForeignKeys[1].RefTable = ItemsTable
|
||||
|
||||
2593
backend/internal/data/ent/mutation.go
generated
6
backend/internal/data/ent/predicate/predicate.go
generated
@@ -27,6 +27,9 @@ type Item func(*sql.Selector)
|
||||
// ItemField is the predicate function for itemfield builders.
|
||||
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)
|
||||
|
||||
@@ -39,5 +42,8 @@ type MaintenanceEntry func(*sql.Selector)
|
||||
// Notifier is the predicate function for notifier builders.
|
||||
type Notifier func(*sql.Selector)
|
||||
|
||||
// TemplateField is the predicate function for templatefield builders.
|
||||
type TemplateField func(*sql.Selector)
|
||||
|
||||
// User is the predicate function for user builders.
|
||||
type User func(*sql.Selector)
|
||||
|
||||
156
backend/internal/data/ent/runtime.go
generated
@@ -12,11 +12,13 @@ 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/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/templatefield"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
|
||||
)
|
||||
|
||||
@@ -310,6 +312,97 @@ func init() {
|
||||
itemfieldDescID := itemfieldMixinFields0[0].Descriptor()
|
||||
// itemfield.DefaultID holds the default value on creation for the id field.
|
||||
itemfield.DefaultID = itemfieldDescID.Default.(func() uuid.UUID)
|
||||
itemtemplateMixin := schema.ItemTemplate{}.Mixin()
|
||||
itemtemplateMixinFields0 := itemtemplateMixin[0].Fields()
|
||||
_ = itemtemplateMixinFields0
|
||||
itemtemplateMixinFields1 := itemtemplateMixin[1].Fields()
|
||||
_ = itemtemplateMixinFields1
|
||||
itemtemplateFields := schema.ItemTemplate{}.Fields()
|
||||
_ = itemtemplateFields
|
||||
// itemtemplateDescCreatedAt is the schema descriptor for created_at field.
|
||||
itemtemplateDescCreatedAt := itemtemplateMixinFields0[1].Descriptor()
|
||||
// itemtemplate.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||
itemtemplate.DefaultCreatedAt = itemtemplateDescCreatedAt.Default.(func() time.Time)
|
||||
// itemtemplateDescUpdatedAt is the schema descriptor for updated_at field.
|
||||
itemtemplateDescUpdatedAt := itemtemplateMixinFields0[2].Descriptor()
|
||||
// itemtemplate.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||
itemtemplate.DefaultUpdatedAt = itemtemplateDescUpdatedAt.Default.(func() time.Time)
|
||||
// itemtemplate.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||
itemtemplate.UpdateDefaultUpdatedAt = itemtemplateDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||
// itemtemplateDescName is the schema descriptor for name field.
|
||||
itemtemplateDescName := itemtemplateMixinFields1[0].Descriptor()
|
||||
// itemtemplate.NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
itemtemplate.NameValidator = func() func(string) error {
|
||||
validators := itemtemplateDescName.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
|
||||
}
|
||||
}()
|
||||
// itemtemplateDescDescription is the schema descriptor for description field.
|
||||
itemtemplateDescDescription := itemtemplateMixinFields1[1].Descriptor()
|
||||
// itemtemplate.DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
itemtemplate.DescriptionValidator = itemtemplateDescDescription.Validators[0].(func(string) error)
|
||||
// itemtemplateDescNotes is the schema descriptor for notes field.
|
||||
itemtemplateDescNotes := itemtemplateFields[0].Descriptor()
|
||||
// itemtemplate.NotesValidator is a validator for the "notes" field. It is called by the builders before save.
|
||||
itemtemplate.NotesValidator = itemtemplateDescNotes.Validators[0].(func(string) error)
|
||||
// itemtemplateDescDefaultQuantity is the schema descriptor for default_quantity field.
|
||||
itemtemplateDescDefaultQuantity := itemtemplateFields[1].Descriptor()
|
||||
// itemtemplate.DefaultDefaultQuantity holds the default value on creation for the default_quantity field.
|
||||
itemtemplate.DefaultDefaultQuantity = itemtemplateDescDefaultQuantity.Default.(int)
|
||||
// itemtemplateDescDefaultInsured is the schema descriptor for default_insured field.
|
||||
itemtemplateDescDefaultInsured := itemtemplateFields[2].Descriptor()
|
||||
// itemtemplate.DefaultDefaultInsured holds the default value on creation for the default_insured field.
|
||||
itemtemplate.DefaultDefaultInsured = itemtemplateDescDefaultInsured.Default.(bool)
|
||||
// itemtemplateDescDefaultName is the schema descriptor for default_name field.
|
||||
itemtemplateDescDefaultName := itemtemplateFields[3].Descriptor()
|
||||
// itemtemplate.DefaultNameValidator is a validator for the "default_name" field. It is called by the builders before save.
|
||||
itemtemplate.DefaultNameValidator = itemtemplateDescDefaultName.Validators[0].(func(string) error)
|
||||
// itemtemplateDescDefaultDescription is the schema descriptor for default_description field.
|
||||
itemtemplateDescDefaultDescription := itemtemplateFields[4].Descriptor()
|
||||
// itemtemplate.DefaultDescriptionValidator is a validator for the "default_description" field. It is called by the builders before save.
|
||||
itemtemplate.DefaultDescriptionValidator = itemtemplateDescDefaultDescription.Validators[0].(func(string) error)
|
||||
// itemtemplateDescDefaultManufacturer is the schema descriptor for default_manufacturer field.
|
||||
itemtemplateDescDefaultManufacturer := itemtemplateFields[5].Descriptor()
|
||||
// itemtemplate.DefaultManufacturerValidator is a validator for the "default_manufacturer" field. It is called by the builders before save.
|
||||
itemtemplate.DefaultManufacturerValidator = itemtemplateDescDefaultManufacturer.Validators[0].(func(string) error)
|
||||
// itemtemplateDescDefaultModelNumber is the schema descriptor for default_model_number field.
|
||||
itemtemplateDescDefaultModelNumber := itemtemplateFields[6].Descriptor()
|
||||
// itemtemplate.DefaultModelNumberValidator is a validator for the "default_model_number" field. It is called by the builders before save.
|
||||
itemtemplate.DefaultModelNumberValidator = itemtemplateDescDefaultModelNumber.Validators[0].(func(string) error)
|
||||
// itemtemplateDescDefaultLifetimeWarranty is the schema descriptor for default_lifetime_warranty field.
|
||||
itemtemplateDescDefaultLifetimeWarranty := itemtemplateFields[7].Descriptor()
|
||||
// itemtemplate.DefaultDefaultLifetimeWarranty holds the default value on creation for the default_lifetime_warranty field.
|
||||
itemtemplate.DefaultDefaultLifetimeWarranty = itemtemplateDescDefaultLifetimeWarranty.Default.(bool)
|
||||
// itemtemplateDescDefaultWarrantyDetails is the schema descriptor for default_warranty_details field.
|
||||
itemtemplateDescDefaultWarrantyDetails := itemtemplateFields[8].Descriptor()
|
||||
// itemtemplate.DefaultWarrantyDetailsValidator is a validator for the "default_warranty_details" field. It is called by the builders before save.
|
||||
itemtemplate.DefaultWarrantyDetailsValidator = itemtemplateDescDefaultWarrantyDetails.Validators[0].(func(string) error)
|
||||
// itemtemplateDescIncludeWarrantyFields is the schema descriptor for include_warranty_fields field.
|
||||
itemtemplateDescIncludeWarrantyFields := itemtemplateFields[9].Descriptor()
|
||||
// itemtemplate.DefaultIncludeWarrantyFields holds the default value on creation for the include_warranty_fields field.
|
||||
itemtemplate.DefaultIncludeWarrantyFields = itemtemplateDescIncludeWarrantyFields.Default.(bool)
|
||||
// itemtemplateDescIncludePurchaseFields is the schema descriptor for include_purchase_fields field.
|
||||
itemtemplateDescIncludePurchaseFields := itemtemplateFields[10].Descriptor()
|
||||
// itemtemplate.DefaultIncludePurchaseFields holds the default value on creation for the include_purchase_fields field.
|
||||
itemtemplate.DefaultIncludePurchaseFields = itemtemplateDescIncludePurchaseFields.Default.(bool)
|
||||
// itemtemplateDescIncludeSoldFields is the schema descriptor for include_sold_fields field.
|
||||
itemtemplateDescIncludeSoldFields := itemtemplateFields[11].Descriptor()
|
||||
// itemtemplate.DefaultIncludeSoldFields holds the default value on creation for the include_sold_fields field.
|
||||
itemtemplate.DefaultIncludeSoldFields = itemtemplateDescIncludeSoldFields.Default.(bool)
|
||||
// itemtemplateDescID is the schema descriptor for id field.
|
||||
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
|
||||
@@ -504,6 +597,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)
|
||||
templatefieldMixin := schema.TemplateField{}.Mixin()
|
||||
templatefieldMixinFields0 := templatefieldMixin[0].Fields()
|
||||
_ = templatefieldMixinFields0
|
||||
templatefieldMixinFields1 := templatefieldMixin[1].Fields()
|
||||
_ = templatefieldMixinFields1
|
||||
templatefieldFields := schema.TemplateField{}.Fields()
|
||||
_ = templatefieldFields
|
||||
// templatefieldDescCreatedAt is the schema descriptor for created_at field.
|
||||
templatefieldDescCreatedAt := templatefieldMixinFields0[1].Descriptor()
|
||||
// templatefield.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||
templatefield.DefaultCreatedAt = templatefieldDescCreatedAt.Default.(func() time.Time)
|
||||
// templatefieldDescUpdatedAt is the schema descriptor for updated_at field.
|
||||
templatefieldDescUpdatedAt := templatefieldMixinFields0[2].Descriptor()
|
||||
// templatefield.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||
templatefield.DefaultUpdatedAt = templatefieldDescUpdatedAt.Default.(func() time.Time)
|
||||
// templatefield.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||
templatefield.UpdateDefaultUpdatedAt = templatefieldDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||
// templatefieldDescName is the schema descriptor for name field.
|
||||
templatefieldDescName := templatefieldMixinFields1[0].Descriptor()
|
||||
// templatefield.NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
templatefield.NameValidator = func() func(string) error {
|
||||
validators := templatefieldDescName.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
|
||||
}
|
||||
}()
|
||||
// templatefieldDescDescription is the schema descriptor for description field.
|
||||
templatefieldDescDescription := templatefieldMixinFields1[1].Descriptor()
|
||||
// templatefield.DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
templatefield.DescriptionValidator = templatefieldDescDescription.Validators[0].(func(string) error)
|
||||
// templatefieldDescTextValue is the schema descriptor for text_value field.
|
||||
templatefieldDescTextValue := templatefieldFields[1].Descriptor()
|
||||
// templatefield.TextValueValidator is a validator for the "text_value" field. It is called by the builders before save.
|
||||
templatefield.TextValueValidator = templatefieldDescTextValue.Validators[0].(func(string) error)
|
||||
// templatefieldDescID is the schema descriptor for id field.
|
||||
templatefieldDescID := templatefieldMixinFields0[0].Descriptor()
|
||||
// templatefield.DefaultID holds the default value on creation for the id field.
|
||||
templatefield.DefaultID = templatefieldDescID.Default.(func() uuid.UUID)
|
||||
userMixin := schema.User{}.Mixin()
|
||||
userMixinFields0 := userMixin[0].Fields()
|
||||
_ = userMixinFields0
|
||||
@@ -558,21 +698,7 @@ func init() {
|
||||
// userDescPassword is the schema descriptor for password field.
|
||||
userDescPassword := userFields[2].Descriptor()
|
||||
// user.PasswordValidator is a validator for the "password" field. It is called by the builders before save.
|
||||
user.PasswordValidator = func() func(string) error {
|
||||
validators := userDescPassword.Validators
|
||||
fns := [...]func(string) error{
|
||||
validators[0].(func(string) error),
|
||||
validators[1].(func(string) error),
|
||||
}
|
||||
return func(password string) error {
|
||||
for _, fn := range fns {
|
||||
if err := fn(password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
user.PasswordValidator = userDescPassword.Validators[0].(func(string) error)
|
||||
// userDescIsSuperuser is the schema descriptor for is_superuser field.
|
||||
userDescIsSuperuser := userFields[3].Descriptor()
|
||||
// user.DefaultIsSuperuser holds the default value on creation for the is_superuser field.
|
||||
|
||||
@@ -48,6 +48,7 @@ func (Group) Edges() []ent.Edge {
|
||||
owned("labels", Label.Type),
|
||||
owned("invitation_tokens", GroupInvitationToken.Type),
|
||||
owned("notifiers", Notifier.Type),
|
||||
owned("item_templates", ItemTemplate.Type),
|
||||
// $scaffold_edge
|
||||
}
|
||||
}
|
||||
|
||||
111
backend/internal/data/ent/schema/item_template.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/entsql"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema/mixins"
|
||||
)
|
||||
|
||||
// ItemTemplate holds the schema definition for the ItemTemplate entity.
|
||||
type ItemTemplate struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (ItemTemplate) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.BaseMixin{},
|
||||
mixins.DetailsMixin{},
|
||||
GroupMixin{ref: "item_templates"},
|
||||
}
|
||||
}
|
||||
|
||||
func (ItemTemplate) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("name"),
|
||||
}
|
||||
}
|
||||
|
||||
// Fields of the ItemTemplate.
|
||||
func (ItemTemplate) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
// Notes for the template (instructions, hints, etc.)
|
||||
field.String("notes").
|
||||
MaxLen(1000).
|
||||
Optional(),
|
||||
|
||||
// ------------------------------------
|
||||
// Default values for item fields
|
||||
field.Int("default_quantity").
|
||||
Default(1),
|
||||
field.Bool("default_insured").
|
||||
Default(false),
|
||||
|
||||
// ------------------------------------
|
||||
// Default item name/description (templates for item identity)
|
||||
field.String("default_name").
|
||||
MaxLen(255).
|
||||
Optional().
|
||||
Comment("Default name template for items (can use placeholders)"),
|
||||
field.Text("default_description").
|
||||
MaxLen(1000).
|
||||
Optional().
|
||||
Comment("Default description for items created from this template"),
|
||||
|
||||
// ------------------------------------
|
||||
// Default item identification
|
||||
field.String("default_manufacturer").
|
||||
MaxLen(255).
|
||||
Optional(),
|
||||
field.String("default_model_number").
|
||||
MaxLen(255).
|
||||
Optional().
|
||||
Comment("Default model number for items created from this template"),
|
||||
|
||||
// ------------------------------------
|
||||
// Default warranty settings
|
||||
field.Bool("default_lifetime_warranty").
|
||||
Default(false),
|
||||
field.Text("default_warranty_details").
|
||||
MaxLen(1000).
|
||||
Optional(),
|
||||
|
||||
// ------------------------------------
|
||||
// Template metadata
|
||||
field.Bool("include_warranty_fields").
|
||||
Default(false).
|
||||
Comment("Whether to include warranty fields in items created from this template"),
|
||||
field.Bool("include_purchase_fields").
|
||||
Default(false).
|
||||
Comment("Whether to include purchase fields in items created from this template"),
|
||||
field.Bool("include_sold_fields").
|
||||
Default(false).
|
||||
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{}).
|
||||
Optional().
|
||||
Comment("Default label IDs for items created from this template"),
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the ItemTemplate.
|
||||
func (ItemTemplate) Edges() []ent.Edge {
|
||||
owned := func(s string, t any) ent.Edge {
|
||||
return edge.To(s, t).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
})
|
||||
}
|
||||
|
||||
return []ent.Edge{
|
||||
owned("fields", TemplateField.Type),
|
||||
// Default location for items created from this template
|
||||
edge.To("location", Location.Type).
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
41
backend/internal/data/ent/schema/template_field.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema/mixins"
|
||||
)
|
||||
|
||||
// TemplateField holds the schema definition for the TemplateField entity.
|
||||
// Template fields define custom fields that will be added to items created from a template.
|
||||
type TemplateField struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (TemplateField) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.BaseMixin{},
|
||||
mixins.DetailsMixin{},
|
||||
}
|
||||
}
|
||||
|
||||
// Fields of the TemplateField.
|
||||
func (TemplateField) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Enum("type").
|
||||
Values("text"),
|
||||
field.String("text_value").
|
||||
MaxLen(500).
|
||||
Optional(),
|
||||
}
|
||||
}
|
||||
|
||||
// Edges of the TemplateField.
|
||||
func (TemplateField) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.From("item_template", ItemTemplate.Type).
|
||||
Ref("fields").
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"entgo.io/ent/dialect/entsql"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
"entgo.io/ent/schema/mixin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema/mixins"
|
||||
@@ -34,7 +35,8 @@ func (User) Fields() []ent.Field {
|
||||
Unique(),
|
||||
field.String("password").
|
||||
MaxLen(255).
|
||||
NotEmpty().
|
||||
Nillable().
|
||||
Optional().
|
||||
Sensitive(),
|
||||
field.Bool("is_superuser").
|
||||
Default(false),
|
||||
@@ -45,8 +47,19 @@ func (User) Fields() []ent.Field {
|
||||
Values("user", "owner"),
|
||||
field.Time("activated_on").
|
||||
Optional(),
|
||||
field.JSON("settings", UserSettings{}).
|
||||
Optional(),
|
||||
// OIDC identity mapping fields (issuer + subject)
|
||||
field.String("oidc_issuer").
|
||||
Optional().
|
||||
Nillable(),
|
||||
field.String("oidc_subject").
|
||||
Optional().
|
||||
Nillable(),
|
||||
}
|
||||
}
|
||||
|
||||
func (User) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("oidc_issuer", "oidc_subject").Unique(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,28 +77,6 @@ func (User) Edges() []ent.Edge {
|
||||
}
|
||||
}
|
||||
|
||||
type UserSettings struct {
|
||||
Theme string `json:"theme"`
|
||||
ItemsPerPage int `json:"itemsPerPage"`
|
||||
Locale string `json:"locale"`
|
||||
ShowDetails bool `json:"showDetails"`
|
||||
ShowEmpty bool `json:"showEmpty"`
|
||||
EditorAdvancedView bool `json:"editorAdvancedView"`
|
||||
ItemDisplayView string `json:"itemDisplayView"`
|
||||
ItemsPerTablePage int `json:"itemsPerTablePage"`
|
||||
DisplayLegacyHeader bool `json:"displayLegacyHeader"`
|
||||
Language string `json:"language"`
|
||||
OverrideFormatLocale string `json:"overrideFormatLocale"`
|
||||
DuplicateSettings DuplicateSettings `json:"duplicateSettings"`
|
||||
}
|
||||
|
||||
type DuplicateSettings struct {
|
||||
CopyMaintenance bool `json:"copyMaintenance"`
|
||||
CopyAttachments bool `json:"copyAttachments"`
|
||||
CopyCustomFields bool `json:"copyCustomFields"`
|
||||
CopyPrefixOverride string `json:"copyPrefixOverride"`
|
||||
}
|
||||
|
||||
// UserMixin when embedded in an ent.Schema, adds a reference to
|
||||
// the Group entity.
|
||||
type UserMixin struct {
|
||||
@@ -105,14 +96,14 @@ func (g UserMixin) Fields() []ent.Field {
|
||||
}
|
||||
|
||||
func (g UserMixin) Edges() []ent.Edge {
|
||||
edge := edge.From("user", User.Type).
|
||||
e := edge.From("user", User.Type).
|
||||
Ref(g.ref).
|
||||
Unique().
|
||||
Required()
|
||||
|
||||
if g.field != "" {
|
||||
edge = edge.Field(g.field)
|
||||
e = e.Field(g.field)
|
||||
}
|
||||
|
||||
return []ent.Edge{edge}
|
||||
return []ent.Edge{e}
|
||||
}
|
||||
|
||||
201
backend/internal/data/ent/templatefield.go
generated
Normal file
@@ -0,0 +1,201 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/itemtemplate"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/templatefield"
|
||||
)
|
||||
|
||||
// TemplateField is the model entity for the TemplateField schema.
|
||||
type TemplateField struct {
|
||||
config `json:"-"`
|
||||
// ID of the ent.
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
// CreatedAt holds the value of the "created_at" field.
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// UpdatedAt holds the value of the "updated_at" field.
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
// Name holds the value of the "name" field.
|
||||
Name string `json:"name,omitempty"`
|
||||
// Description holds the value of the "description" field.
|
||||
Description string `json:"description,omitempty"`
|
||||
// Type holds the value of the "type" field.
|
||||
Type templatefield.Type `json:"type,omitempty"`
|
||||
// TextValue holds the value of the "text_value" field.
|
||||
TextValue string `json:"text_value,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the TemplateFieldQuery when eager-loading is set.
|
||||
Edges TemplateFieldEdges `json:"edges"`
|
||||
item_template_fields *uuid.UUID
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// TemplateFieldEdges holds the relations/edges for other nodes in the graph.
|
||||
type TemplateFieldEdges struct {
|
||||
// ItemTemplate holds the value of the item_template edge.
|
||||
ItemTemplate *ItemTemplate `json:"item_template,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [1]bool
|
||||
}
|
||||
|
||||
// ItemTemplateOrErr returns the ItemTemplate value or an error if the edge
|
||||
// was not loaded in eager-loading, or loaded but was not found.
|
||||
func (e TemplateFieldEdges) ItemTemplateOrErr() (*ItemTemplate, error) {
|
||||
if e.ItemTemplate != nil {
|
||||
return e.ItemTemplate, nil
|
||||
} else if e.loadedTypes[0] {
|
||||
return nil, &NotFoundError{label: itemtemplate.Label}
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "item_template"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*TemplateField) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case templatefield.FieldName, templatefield.FieldDescription, templatefield.FieldType, templatefield.FieldTextValue:
|
||||
values[i] = new(sql.NullString)
|
||||
case templatefield.FieldCreatedAt, templatefield.FieldUpdatedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
case templatefield.FieldID:
|
||||
values[i] = new(uuid.UUID)
|
||||
case templatefield.ForeignKeys[0]: // item_template_fields
|
||||
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the TemplateField fields.
|
||||
func (_m *TemplateField) 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 templatefield.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 templatefield.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 templatefield.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 templatefield.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 templatefield.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 templatefield.FieldType:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field type", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Type = templatefield.Type(value.String)
|
||||
}
|
||||
case templatefield.FieldTextValue:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field text_value", values[i])
|
||||
} else if value.Valid {
|
||||
_m.TextValue = value.String
|
||||
}
|
||||
case templatefield.ForeignKeys[0]:
|
||||
if value, ok := values[i].(*sql.NullScanner); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field item_template_fields", values[i])
|
||||
} else if value.Valid {
|
||||
_m.item_template_fields = new(uuid.UUID)
|
||||
*_m.item_template_fields = *value.S.(*uuid.UUID)
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the ent.Value that was dynamically selected and assigned to the TemplateField.
|
||||
// This includes values selected through modifiers, order, etc.
|
||||
func (_m *TemplateField) Value(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// QueryItemTemplate queries the "item_template" edge of the TemplateField entity.
|
||||
func (_m *TemplateField) QueryItemTemplate() *ItemTemplateQuery {
|
||||
return NewTemplateFieldClient(_m.config).QueryItemTemplate(_m)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this TemplateField.
|
||||
// Note that you need to call TemplateField.Unwrap() before calling this method if this TemplateField
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (_m *TemplateField) Update() *TemplateFieldUpdateOne {
|
||||
return NewTemplateFieldClient(_m.config).UpdateOne(_m)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the TemplateField 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 *TemplateField) Unwrap() *TemplateField {
|
||||
_tx, ok := _m.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: TemplateField is not a transactional entity")
|
||||
}
|
||||
_m.config.driver = _tx.drv
|
||||
return _m
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (_m *TemplateField) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("TemplateField(")
|
||||
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||
builder.WriteString("created_at=")
|
||||
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("updated_at=")
|
||||
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("name=")
|
||||
builder.WriteString(_m.Name)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("description=")
|
||||
builder.WriteString(_m.Description)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("type=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Type))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("text_value=")
|
||||
builder.WriteString(_m.TextValue)
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// TemplateFields is a parsable slice of TemplateField.
|
||||
type TemplateFields []*TemplateField
|
||||
165
backend/internal/data/ent/templatefield/templatefield.go
generated
Normal file
@@ -0,0 +1,165 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package templatefield
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the templatefield type in the database.
|
||||
Label = "template_field"
|
||||
// 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.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldName holds the string denoting the name field in the database.
|
||||
FieldName = "name"
|
||||
// FieldDescription holds the string denoting the description field in the database.
|
||||
FieldDescription = "description"
|
||||
// FieldType holds the string denoting the type field in the database.
|
||||
FieldType = "type"
|
||||
// FieldTextValue holds the string denoting the text_value field in the database.
|
||||
FieldTextValue = "text_value"
|
||||
// EdgeItemTemplate holds the string denoting the item_template edge name in mutations.
|
||||
EdgeItemTemplate = "item_template"
|
||||
// Table holds the table name of the templatefield in the database.
|
||||
Table = "template_fields"
|
||||
// ItemTemplateTable is the table that holds the item_template relation/edge.
|
||||
ItemTemplateTable = "template_fields"
|
||||
// ItemTemplateInverseTable is the table name for the ItemTemplate entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "itemtemplate" package.
|
||||
ItemTemplateInverseTable = "item_templates"
|
||||
// ItemTemplateColumn is the table column denoting the item_template relation/edge.
|
||||
ItemTemplateColumn = "item_template_fields"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for templatefield fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldDescription,
|
||||
FieldType,
|
||||
FieldTextValue,
|
||||
}
|
||||
|
||||
// ForeignKeys holds the SQL foreign-keys that are owned by the "template_fields"
|
||||
// table and are not defined as standalone fields in the schema.
|
||||
var ForeignKeys = []string{
|
||||
"item_template_fields",
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ForeignKeys {
|
||||
if column == ForeignKeys[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
|
||||
DescriptionValidator func(string) error
|
||||
// TextValueValidator is a validator for the "text_value" field. It is called by the builders before save.
|
||||
TextValueValidator func(string) error
|
||||
// DefaultID holds the default value on creation for the "id" field.
|
||||
DefaultID func() uuid.UUID
|
||||
)
|
||||
|
||||
// Type defines the type for the "type" enum field.
|
||||
type Type string
|
||||
|
||||
// Type values.
|
||||
const (
|
||||
TypeText Type = "text"
|
||||
)
|
||||
|
||||
func (_type Type) String() string {
|
||||
return string(_type)
|
||||
}
|
||||
|
||||
// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save.
|
||||
func TypeValidator(_type Type) error {
|
||||
switch _type {
|
||||
case TypeText:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("templatefield: invalid enum value for type field: %q", _type)
|
||||
}
|
||||
}
|
||||
|
||||
// OrderOption defines the ordering options for the TemplateField queries.
|
||||
type OrderOption func(*sql.Selector)
|
||||
|
||||
// ByID orders the results by the id field.
|
||||
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCreatedAt orders the results by the created_at field.
|
||||
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUpdatedAt orders the results by the updated_at field.
|
||||
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByName orders the results by the name field.
|
||||
func ByName(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldName, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDescription orders the results by the description field.
|
||||
func ByDescription(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDescription, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByType orders the results by the type field.
|
||||
func ByType(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldType, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByTextValue orders the results by the text_value field.
|
||||
func ByTextValue(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldTextValue, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByItemTemplateField orders the results by item_template field.
|
||||
func ByItemTemplateField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newItemTemplateStep(), sql.OrderByField(field, opts...))
|
||||
}
|
||||
}
|
||||
func newItemTemplateStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(ItemTemplateInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, ItemTemplateTable, ItemTemplateColumn),
|
||||
)
|
||||
}
|
||||