Compare commits

...

87 Commits

Author SHA1 Message Date
Hayden
0e48256b77 use comma deliminator 2023-04-01 12:21:05 -08:00
Hayden
c41a43d804 fix columns in docs 2023-04-01 12:13:24 -08:00
Hayden
3ed72daa64 support YYYY/MM/DD format for imports 2023-04-01 12:09:20 -08:00
Hayden
564b8a2725 fix listener for resetItemDateTimes 2023-04-01 12:09:11 -08:00
Hayden
4a9bcde9ea fix insufficiently large max height for cards 2023-04-01 12:08:44 -08:00
Hayden
e1e04d49aa update git ignore 2023-03-25 11:20:38 -08:00
Hayden
9020587c9e set working directory 2023-03-25 11:17:07 -08:00
Hayden
bd0db1ea37 remove go tidy 2023-03-25 11:13:23 -08:00
Hayden
d2985ff72c go tidy 2023-03-25 11:12:48 -08:00
Hayden
8c57ff841e fix: redirect issues for authorized users (#374) 2023-03-25 11:07:22 -08:00
renovate[bot]
0264bfb8c1 chore(deps): update dependency mkdocs-material to v9.1.4 (#371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 08:03:56 -08:00
Hayden
5dd6844536 feat: change shit to things (#369) 2023-03-23 19:10:19 -08:00
Hayden
0f8db862b4 feat: pwa support (#366)
* add PWA support

* fix broken URLs for query

* remove unused variable

* restore authURL
2023-03-23 10:27:12 -08:00
Hayden
be6b5c9c56 run frontend build before publish via goreleaser 2023-03-23 09:41:15 -08:00
Hayden
faed343eda fix: cookie-auth-issues (#365)
* fix session clearing on error

* use singleton context to manage user state

* implement remember-me functionality

* fix errors

* fix more errors
2023-03-22 21:52:25 -08:00
Hayden
ed1230e17d feat: goreleaser + remove cgo dependency (#363)
* wip: goreleaser

* update image path

* spelling

* set working dir

* change main.go

* remove unused field

* drop cgo requirement

* remove unused workflow step

* generate code

* drop cgo from docker file

* update publish workflow

* annotate as unfinished
2023-03-22 20:49:49 -08:00
zodac
2d768e2b9c feat Adding NZD currency (#360)
* Adding NZD as currency option

* Updating frontend

* Sorting alphabetically

* Fixing typo
2023-03-22 20:26:51 -08:00
Hayden
40e76bac0c feat: filter details for zero values (#364)
* filter details for zero values

* ensure exhaustive checks

* update event listener to only bind when collapsable
2023-03-22 19:05:13 -08:00
Hayden
840d220d4f feat: use notifiers on schedule (#362)
* fix potential memory leak with time.After

* add new background service to manage scheduled notifications

* update docs

* remove old js reference

* closes #278

* tidy
2023-03-21 11:32:48 -08:00
renovate[bot]
975e636fb6 fix(deps): update module github.com/swaggo/swag to v1.8.11 (#361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-21 11:27:40 -08:00
renovate[bot]
d1076baf84 fix(deps): update module github.com/swaggo/http-swagger to v2 (#358)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-21 11:26:46 -08:00
renovate[bot]
40fcef4e9b chore(deps): update dependency typescript to v5 (#355)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-21 11:23:30 -08:00
Hayden
97fb94d231 fix: refactoring errors (#359)
* #352 - require one date to be set to save

* fix many type regressions
2023-03-20 21:48:22 -08:00
renovate[bot]
4a8ba6231d fix(deps): update module entgo.io/ent to v0.11.10 (#328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 20:32:48 -08:00
Hayden
db80f8a159 chore: refactor api endpoints (#339)
* move typegen code

* update taskfile to fix code-gen caches and use 'dir' attribute

* enable dumping stack traces for errors

* log request start and stop

* set zerolog stack handler

* fix routes function

* refactor context adapters to use requests directly

* change some method signatures to support GID

* start requiring validation tags

* first pass on updating handlers to use adapters

* add errs package

* code gen

* tidy

* rework API to use external server package
2023-03-20 20:32:10 -08:00
renovate[bot]
184b494fc3 fix(deps): update module github.com/go-playground/validator/v10 to v10.12.0 (#357)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:43:56 -08:00
renovate[bot]
5a3fa23332 fix(deps): update module github.com/swaggo/http-swagger to v1.3.4 (#356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:43:49 -08:00
renovate[bot]
ef0690d511 chore(deps): update actions/setup-go action to v4 (#351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:43:33 -08:00
renovate[bot]
9e55c880f6 chore(deps): update dependency @types/dompurify to v3 (#346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:43:04 -08:00
renovate[bot]
cb9b20e2d2 fix(deps): update module github.com/ardanlabs/conf/v3 to v3.1.5 (#341)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:42:49 -08:00
renovate[bot]
a79e780b4e chore(deps): update dependency mkdocs-material to v9.1.3 (#340)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:42:38 -08:00
renovate[bot]
90cbb9bfd1 fix(deps): update module ariga.io/atlas to v0.10.0 (#327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:42:25 -08:00
Vitalij Dovhanyc
dc08dbbd7a feat: add czech currency (#323) 2023-03-20 18:42:11 -08:00
renovate[bot]
1f47d96e4c chore(deps): update dependency nuxt to v3.2.3 (#326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-07 10:12:32 -09:00
Hayden
23b5892aef feat: Notifiers CRUD (#337)
* introduce scaffold for new models

* wip: shoutrrr wrapper (may remove)

* update schema files

* gen: ent code

* gen: migrations

* go mod tidy

* add group_id to notifier

* db migration

* new mapper helpers

* notifier repo

* introduce experimental adapter pattern for hdlrs

* refactor adapters to fit more common use cases

* new routes for notifiers

* update errors to fix validation panic

* go tidy

* reverse checkbox label display

* wip: notifiers UI

* use badges instead of text

* improve documentation

* add scaffold schema reference

* remove notifier service

* refactor schema folder

* support group edges via scaffold

* delete test file

* include link to API docs

* audit and update documentation + improve format

* refactor schema edges

* refactor

* add custom validator

* set validate + order fields by name

* fix failing tests
2023-03-06 21:18:58 -09:00
renovate[bot]
2665b666f1 fix(deps): update github.com/gocarina/gocsv digest to 70c27cb (#308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-06 10:09:17 -09:00
renovate[bot]
14315cb88a fix(deps): update module golang.org/x/crypto to v0.7.0 (#336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-05 16:49:16 -09:00
Hayden
cf536393f5 fix datetime display issues (again) (#324) 2023-02-27 19:52:56 -09:00
Hayden
025521431e feat: add scheduled maintenance tasks (#320)
* add scheduled maintenance tasks

* fix failing typecheck
2023-02-26 18:42:23 -09:00
Hayden
70297b9d27 feat: more-currency-support (#316)
* add polish and turkish lira

* add romanian lei

* code-gen
2023-02-25 18:13:52 -09:00
Hayden
729293745f fix: table row background (#315) 2023-02-25 18:07:03 -09:00
Hayden
a6bcb36c5b feat: import export rewrite (#290)
* WIP: initial work

* refactoring

* fix failing JS tests

* update import docs

* fix import headers

* fix column headers

* update refs on import

* remove demo status

* finnnneeeee

* formatting
2023-02-25 17:54:40 -09:00
Adrian
a005fa5b9b feat: add currency swiss francs (#311) 2023-02-25 10:46:27 -09:00
renovate[bot]
a2dfa9dcef chore(deps): update dependency vitest to ^0.29.0 (#312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 10:45:56 -09:00
renovate[bot]
32216f63bd fix(deps): update module github.com/stretchr/testify to v1.8.2 (#313)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 10:45:35 -09:00
renovate[bot]
ef190e26df fix(deps): update github.com/gocarina/gocsv digest to bcce7dc (#307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-19 18:04:36 -09:00
Hayden
c3e3702a7e refactor: change icon for locations to tree view 2023-02-18 10:56:28 -09:00
Hayden
fb57120067 feat: support item nesting in tree view (#306) 2023-02-18 10:55:14 -09:00
Hayden
9d9b05d8a6 fix: several layout issues (#305)
* fix login version issue

* allow wrapping on action menu
2023-02-18 10:09:19 -09:00
Hayden
3ac6c7c858 fix: use item quantity as count mechanism (#304) 2023-02-18 09:57:09 -09:00
Hayden
859d3b9ffe fix label store (#303) 2023-02-18 09:47:04 -09:00
renovate[bot]
6cfa6c9fc8 chore(deps): update dependency nuxt to v3.2.2 (#298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-17 21:58:09 -09:00
Hayden
12975ce26e feat: change auth to use cookies (#301)
* frontend cookie implementation

* accept cookies for authentication

* remove auth store

* add self attr
2023-02-17 21:57:21 -09:00
Hayden
bd321af29f chore: developer cleanup (#300)
* new PR tasks

* add homebox to know words

* formatting

* bump deps

* generate db models

* ts errors

* drop id

* fix accessor

* drop unused time field

* change CI

* add expected error

* add type check

* resolve serveral type errors

* hoise in CI
2023-02-17 21:41:01 -09:00
renovate[bot]
88f9ff90d4 fix(deps): update module entgo.io/ent to v0.11.8 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:43:44 -09:00
renovate[bot]
354f1adbee chore(deps): update dependency nuxt to v3.2.0 (#259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:43:22 -09:00
renovate[bot]
2a62a43493 fix(deps): update dependency dompurify to v3 (#277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:42:48 -09:00
Kyle Brown
673db41f37 build(docker): add image source label (#288)
This adds the `org.opencontainers.image.source` label pointing to this repository for cross-referencing information. For example: https://github.com/renovatebot/renovate/blob/main/lib/modules/datasource/docker/readme.md
2023-02-16 10:36:40 -09:00
renovate[bot]
830ce2b0a9 fix(deps): update module github.com/ardanlabs/conf/v3 to v3.1.4 (#291)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:35:54 -09:00
Hayden
e8e6a425dd fix: button display on light mode (#293) 2023-02-16 10:28:52 -09:00
Hayden
da00db0608 fix: code generation and type processing (#292)
regular expressions are order specific and when applied in a random order you can get a variety of outputs. Using a list preserves order and ensures that the data-contracts.ts file is deterministic.
2023-02-16 10:13:09 -09:00
Hayden
efd7069fe4 feat: hide registration button when disabled (#287)
* add allow registration to API Summary

* code gen

* use env for troubleshooting

* disable registration toggle based on backend
2023-02-15 08:58:38 -09:00
Hayden
dd349aa98e fix #285 (#286) 2023-02-15 08:52:13 -09:00
Hayden
607b06d2f2 fix: date and datetime regression (#282)
* use custom types.Date implementation

* fix user registration bug

* remove sanity check

* fix datetime bug
2023-02-15 08:40:35 -09:00
Hayden
44f13f751a docs: update label generator docs 2023-02-13 10:50:49 -09:00
Hayden
986d2c586e refactor: editor page (#276) 2023-02-13 10:43:09 -09:00
Hayden
9361997a42 feat(reporting): bill of materials (#275)
* new reporting service

* API route

* code gen

* get tsv export from tools page

* fix naming
2023-02-13 10:00:29 -09:00
Hayden
2e96d8c4c2 fix favicon error 2023-02-12 15:14:11 -09:00
Hayden
ff75daf6b3 feat: mvp for label generation/printing (#274)
* initial label generator for QR codes

* use dynamic URL parameter
2023-02-12 15:09:31 -09:00
renovate[bot]
c0953bbd26 fix(deps): update module github.com/go-playground/validator/v10 to v10.11.2 (#253)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-10 19:41:39 -09:00
renovate[bot]
1f7e917c70 fix(deps): update module golang.org/x/crypto to v0.6.0 (#267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-10 19:41:29 -09:00
Hayden
6ff2d64996 feat: init tools page (#271) 2023-02-10 19:38:50 -09:00
Hayden
ab22ea6a25 feat: rebuild search UI w/ new filters (#269) 2023-02-09 17:47:41 -09:00
Hayden
ce2fc7712a fix: add custom action for fixing broken date/times (#268) 2023-02-08 17:59:04 -09:00
Hayden
bd933af874 feat: implement selectable view + sortable table (#264) 2023-02-05 14:00:33 -09:00
Hayden
f36f17b57d feat: toggle view of password field (#263) 2023-02-05 12:56:47 -09:00
Hayden
504569bed0 fix: prevents re-creating locations and labels when someone joins group (#262)
* closes #258

* remove debug statement
2023-02-05 12:25:30 -09:00
Hayden
bd06fdafaf feat: enhanced search functions (#260)
* make login case insensitive

* expand query to support by Field and By AID search

* type generation

* new API callers

* rework search to support field queries

* improve unnecessary data fetches

* clear stores on logout

* change verbage

* add labels
2023-02-05 12:12:54 -09:00
Hayden
7b28973c60 fix: tree fixes (#252)
* use case insensitive sort

* support new location selector in create item

* fix incorrect date-time parsing logic
2023-01-29 13:20:18 -09:00
Hayden
cbac17c059 feat: use native date picker + align date formats (#251)
* use native date picker

* use YYYY-MM-DD for date formats
2023-01-28 13:32:39 -09:00
Hayden
6ed1f3695a chore: upgrade deps + code-gen (#249) 2023-01-28 12:03:51 -09:00
Hayden
3d295b5132 feat: locations tree viewer (#248)
* location tree API

* test fixes

* initial tree location elements

* locations tree page

* update meta-data

* code-gen

* store item display preferences

* introduce basic table/card view elements

* codegen

* set parent location during location creation

* add item support for tree query

* refactor tree view

* wip: location selector improvements

* type gen

* rename items -> search

* remove various log statements

* fix markdown rendering for description

* update location selectors

* fix tests

* fix currency tests

* formatting
2023-01-28 11:53:00 -09:00
renovate[bot]
4d220cdd9c chore(deps): update dependency vitest to ^0.28.0 (#244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-26 10:05:09 -09:00
Serios
ad20e4e39b Adding new currency (#243)
Adding Bulgarian lev to list of currencies
2023-01-26 08:59:07 -09:00
renovate[bot]
3e2f6a96bf chore(deps): update dependency nuxt to v3.1.1 (#245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-26 08:58:21 -09:00
renovate[bot]
2ece97c42a fix(deps): update module github.com/swaggo/swag to v1.8.10 (#246)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-26 08:57:54 -09:00
renovate[bot]
a8ee4d421b fix(deps): update module github.com/rs/zerolog to v1.29.0 (#247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-26 08:57:45 -09:00
316 changed files with 23112 additions and 8879 deletions

View File

@@ -10,9 +10,9 @@ jobs:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: "1.20"
- name: Install Task
uses: arduino/setup-task@v1

View File

@@ -4,7 +4,33 @@ on:
workflow_call:
jobs:
Frontend:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2.2.4
with:
version: 6.0.2
- name: Install dependencies
run: pnpm install --shamefully-hoist
working-directory: frontend
- name: Run Lint
run: pnpm run lint:ci
working-directory: frontend
- name: Run Typecheck
run: pnpm run typecheck
working-directory: frontend
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -18,9 +44,9 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: "1.20"
- uses: actions/setup-node@v3
with:
@@ -34,9 +60,5 @@ jobs:
run: pnpm install
working-directory: frontend
- name: Run linter 👀
run: pnpm lint
working-directory: "frontend"
- name: Run Integration Tests
run: task test:ci

View File

@@ -22,9 +22,9 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: "1.20"
- name: Set up QEMU
id: qemu

View File

@@ -1,4 +1,4 @@
name: Build Nightly
name: Publish Dockers
on:
push:
@@ -12,20 +12,9 @@ env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
backend-tests:
name: "Backend Server Tests"
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
frontend-tests:
name: "Frontend and End-to-End Tests"
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
deploy:
name: "Deploy Nightly to Fly.io"
runs-on: ubuntu-latest
needs:
- backend-tests
- frontend-tests
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
@@ -34,9 +23,6 @@ jobs:
publish-nightly:
name: "Publish Nightly"
if: github.event_name != 'release'
needs:
- backend-tests
- frontend-tests
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
with:
tag: nightly
@@ -46,9 +32,6 @@ jobs:
publish-tag:
name: "Publish Tag"
if: github.event_name == 'release'
needs:
- backend-tests
- frontend-tests
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
with:
release: true

View File

@@ -12,4 +12,4 @@ jobs:
frontend-tests:
name: "Frontend and End-to-End Tests"
uses: ./.github/workflows/partial-frontend.yaml
uses: ./.github/workflows/partial-frontend.yaml

51
.github/workflows/tag.yaml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Publish Release
on:
push:
tags:
- v*
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
backend-tests:
name: "Backend Server Tests"
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
frontend-tests:
name: "Frontend and End-to-End Tests"
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
- uses: pnpm/action-setup@v2
with:
version: 7.30.1
- name: Build Frontend and Copy to Backend
working-directory: frontend
run: |
pnpm install --shamefully-hoist
pnpm run build
cp -r ./.output/public ../backend/app/api/static/
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
workdir: "backend"
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

7
.gitignore vendored
View File

@@ -48,4 +48,9 @@ dist
.pnpm-store
backend/app/api/app
backend/app/api/__debug_bin
backend/app/api/__debug_bin
dist/
# Nuxt Publish Dir
backend/app/api/static/public/*
!backend/app/api/static/public/.gitkeep

View File

@@ -0,0 +1,33 @@
---
# yaml-language-server: $schema=https://hay-kot.github.io/scaffold/schema.json
messages:
pre: |
# Ent Model Generation
With Boilerplate!
post: |
Complete!
questions:
- name: "model"
prompt:
message: "What is the name of the model? (PascalCase)"
required: true
- name: "by_group"
prompt:
confirm: "Include a Group Edge? (group_id -> id)"
required: true
rewrites:
- from: 'templates/model.go'
to: 'backend/internal/data/ent/schema/{{ lower .Scaffold.model }}.go'
inject:
- name: "Insert Groups Edge"
path: 'backend/internal/data/ent/schema/group.go'
at: // $scaffold_edge
template: |
{{- if .Scaffold.by_group -}}
owned("{{ lower .Scaffold.model }}s", {{ .Scaffold.model }}.Type),
{{- end -}}

View File

@@ -0,0 +1,40 @@
package schema
import (
"entgo.io/ent"
"github.com/hay-kot/homebox/backend/internal/data/ent/schema/mixins"
)
type {{ .Scaffold.model }} struct {
ent.Schema
}
func ({{ .Scaffold.model }}) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.BaseMixin{},
{{- if .Scaffold.by_group }}
GroupMixin{ref: "{{ snakecase .Scaffold.model }}s"},
{{- end }}
}
}
// Fields of the {{ .Scaffold.model }}.
func ({{ .Scaffold.model }}) Fields() []ent.Field {
return []ent.Field{
// field.String("name").
}
}
// Edges of the {{ .Scaffold.model }}.
func ({{ .Scaffold.model }}) Edges() []ent.Edge {
return []ent.Edge{
// edge.From("group", Group.Type).
}
}
func ({{ .Scaffold.model }}) Indexes() []ent.Index {
return []ent.Index{
// index.Fields("token"),
}
}

View File

@@ -9,7 +9,8 @@
"README.md": "LICENSE, SECURITY.md"
},
"cSpell.words": [
"debughandlers"
"debughandlers",
"Homebox"
],
// use ESLint to format code on save
"editor.formatOnSave": false,

View File

@@ -48,4 +48,10 @@ start command `task: ui:dev`
1. The frontend is a Vue 3 app with Nuxt.js that uses Tailwind and DaisyUI for styling.
2. We're using Vitest for our automated testing. you can run these with `task ui:watch`.
3. Tests require the API server to be running and in some cases the first run will fail due to a race condition. If this happens just run the tests again and they should pass.
3. Tests require the API server to be running and in some cases the first run will fail due to a race condition. If this happens just run the tests again and they should pass.
## Publishing Release
Create a new tag in github with the version number vX.X.X. This will trigger a new release to be created.
Test -> Goreleaser -> Publish Release -> Trigger Docker Builds -> Deploy Docs + Fly.io Demo

View File

@@ -22,7 +22,7 @@ COPY ./backend .
RUN go get -d -v ./...
RUN rm -rf ./app/api/public
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
RUN CGO_ENABLED=1 GOOS=linux go build \
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-o /go/bin/api \
-v ./app/api/*.go
@@ -41,9 +41,10 @@ COPY --from=builder /go/bin/api /app
RUN chmod +x /app/api
LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
EXPOSE 7745
WORKDIR /app
VOLUME [ "/data" ]
ENTRYPOINT [ "/app/api" ]
CMD [ "/data/config.yml" ]
CMD [ "/data/config.yml" ]

View File

@@ -16,10 +16,13 @@
[Configuration & Docker Compose](https://hay-kot.github.io/homebox/quick-start)
```bash
docker run --name=homebox \
--restart=always \
--publish=3100:7745 \
ghcr.io/hay-kot/homebox:latest
docker run -d \
--name homebox \
--restart unless-stopped \
--publish 3100:7745 \
--env TZ=Europe/Bucharest \
--volume /path/to/data/folder/:/data \
ghcr.io/hay-kot/homebox:latest
```
## Credits

View File

@@ -1,7 +1,8 @@
version: "3"
env:
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_pragma=busy_timeout=1000&_pragma=journal_mode=WAL&_fk=1
HBOX_OPTIONS_ALLOW_REGISTRATION: true
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
tasks:
setup:
@@ -26,46 +27,47 @@ tasks:
--modular \
--path ./backend/app/api/static/docs/swagger.json \
--output ./frontend/lib/api/types
- go run ./scripts/process-types/*.go ./frontend/lib/api/types/data-contracts.ts
- go run ./backend/app/tools/typegen/main.go ./frontend/lib/api/types/data-contracts.ts
- cp ./backend/app/api/static/docs/swagger.json docs/docs/api/openapi-2.0.json
sources:
- "./backend/app/api/**/*"
- "./backend/internal/data/**"
- "./backend/internal/services/**/*"
- "./scripts/process-types.py"
generates:
- "./frontend/lib/api/types/data-contracts.ts"
- "./backend/internal/data/ent/schema"
- "./backend/app/api/static/docs/swagger.json"
- "./backend/app/api/static/docs/swagger.yaml"
- "./backend/internal/core/services/**/*"
- "./backend/app/tools/typegen/main.go"
go:run:
desc: Starts the backend api server (depends on generate task)
dir: backend
deps:
- generate
cmds:
- cd backend && go run ./app/api/ {{ .CLI_ARGS }}
- go run ./app/api/ {{ .CLI_ARGS }}
silent: false
go:test:
desc: Runs all go tests using gotestsum - supports passing gotestsum args
dir: backend
cmds:
- cd backend && gotestsum {{ .CLI_ARGS }} ./...
- gotestsum {{ .CLI_ARGS }} ./...
go:coverage:
desc: Runs all go tests with -race flag and generates a coverage report
dir: backend
cmds:
- cd backend && go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
- go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
silent: true
go:tidy:
desc: Runs go mod tidy on the backend
dir: backend
cmds:
- cd backend && go mod tidy
- go mod tidy
go:lint:
desc: Runs golangci-lint
dir: backend
cmds:
- cd backend && golangci-lint run ./...
- golangci-lint run ./...
go:all:
desc: Runs all go test and lint related tasks
@@ -76,19 +78,19 @@ tasks:
go:build:
desc: Builds the backend binary
dir: backend
cmds:
- cd backend && go build -o ../build/backend ./app/api
- go build -o ../build/backend ./app/api
db:generate:
desc: Run Entgo.io Code Generation
dir: backend/internal/
cmds:
- |
cd backend/internal/ && go generate ./... \
go generate ./... \
--template=./data/ent/schema/templates/has_id.tmpl
sources:
- "./backend/internal/data/ent/schema/**/*"
generates:
- "./backend/internal/ent/"
db:migration:
desc: Runs the database diff engine to generate a SQL migration files
@@ -99,13 +101,27 @@ tasks:
ui:watch:
desc: Starts the vitest test runner in watch mode
dir: frontend
cmds:
- cd frontend && pnpm run test:watch
- pnpm run test:watch
ui:dev:
desc: Run frontend development server
dir: frontend
cmds:
- cd frontend && pnpm dev
- pnpm dev
ui:fix:
desc: Runs prettier and eslint on the frontend
dir: frontend
cmds:
- pnpm run lint:fix
ui:check:
desc: Runs type checking
dir: frontend
cmds:
- pnpm run typecheck
test:ci:
desc: Runs end-to-end test on a live server (only for use in CI)
@@ -115,3 +131,12 @@ tasks:
- sleep 5
- cd frontend && pnpm run test:ci
silent: true
pr:
desc: Runs all tasks required for a PR
cmds:
- task: generate
- task: go:all
- task: ui:check
- task: ui:fix
- task: test:ci

2
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
dist/

54
backend/.goreleaser.yaml Normal file
View File

@@ -0,0 +1,54 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# you may remove this if you don't need go generate
- go generate ./...
builds:
- main: ./app/api
env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- amd64
- "386"
- arm
- arm64
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: "386"
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.
name_template: >-
{{ .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
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
# The lines beneath this are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

View File

@@ -8,7 +8,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/pkgs/mailer"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/server"
)
type app struct {
@@ -37,8 +37,11 @@ func new(conf *config.Config) *app {
}
func (a *app) startBgTask(t time.Duration, fn func()) {
timer := time.NewTimer(t)
for {
timer.Reset(t)
a.server.Background(fn)
time.Sleep(t)
<-timer.C
}
}

View File

@@ -2,7 +2,6 @@ package main
import (
"context"
"encoding/csv"
"strings"
"github.com/hay-kot/homebox/backend/internal/core/services"
@@ -10,7 +9,7 @@ import (
)
func (a *app) SetupDemo() {
csvText := `Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Model Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
@@ -19,16 +18,14 @@ func (a *app) SetupDemo() {
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
`
var (
registration = services.UserRegistration{
Email: "demo@example.com",
Name: "Demo",
Password: "demo",
}
)
registration := services.UserRegistration{
Email: "demo@example.com",
Name: "Demo",
Password: "demo",
}
// First check if we've already setup a demo user and skip if so
_, err := a.services.User.Login(context.Background(), registration.Email, registration.Password)
_, err := a.services.User.Login(context.Background(), registration.Email, registration.Password, false)
if err == nil {
return
}
@@ -39,20 +36,10 @@ func (a *app) SetupDemo() {
log.Fatal().Msg("Failed to setup demo")
}
token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password)
token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password, false)
self, _ := a.services.User.GetSelf(context.Background(), token.Raw)
// Read CSV Text
reader := csv.NewReader(strings.NewReader(csvText))
reader.Comma = ','
records, err := reader.ReadAll()
if err != nil {
log.Err(err).Msg("Failed to read CSV")
log.Fatal().Msg("Failed to setup demo")
}
_, err = a.services.Items.CsvImport(context.Background(), self.GroupID, records)
_, err = a.services.Items.CsvImport(context.Background(), self.GroupID, strings.NewReader(csvText))
if err != nil {
log.Err(err).Msg("Failed to import CSV")
log.Fatal().Msg("Failed to setup demo")

View File

@@ -5,9 +5,26 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
)
type Results[T any] struct {
Items []T `json:"items"`
}
func WrapResults[T any](items []T) Results[T] {
return Results[T]{Items: items}
}
type Wrapped struct {
Item interface{} `json:"item"`
}
func Wrap(v any) Wrapped {
return Wrapped{Item: v}
}
func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.maxUploadSize = maxUploadSize
@@ -44,12 +61,13 @@ type (
}
ApiSummary struct {
Healthy bool `json:"health"`
Versions []string `json:"versions"`
Title string `json:"title"`
Message string `json:"message"`
Build Build `json:"build"`
Demo bool `json:"demo"`
Healthy bool `json:"health"`
Versions []string `json:"versions"`
Title string `json:"title"`
Message string `json:"message"`
Build Build `json:"build"`
Demo bool `json:"demo"`
AllowRegistration bool `json:"allowRegistration"`
}
)
@@ -74,19 +92,21 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, options ..
}
// HandleBase godoc
// @Summary Retrieves the basic information about the API
// @Tags Base
// @Produce json
// @Success 200 {object} ApiSummary
// @Router /v1/status [GET]
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) server.HandlerFunc {
//
// @Summary Application Info
// @Tags Base
// @Produce json
// @Success 200 {object} ApiSummary
// @Router /v1/status [GET]
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
return server.Respond(w, http.StatusOK, ApiSummary{
Healthy: ready(),
Title: "Go API Template",
Message: "Welcome to the Go API Template Application!",
Build: build,
Demo: ctrl.isDemo,
return server.JSON(w, http.StatusOK, ApiSummary{
Healthy: ready(),
Title: "Homebox",
Message: "Track, Manage, and Organize your Things",
Build: build,
Demo: ctrl.isDemo,
AllowRegistration: ctrl.allowRegistration,
})
}
}

View File

@@ -21,7 +21,7 @@ func (ctrl *V1Controller) routeID(r *http.Request) (uuid.UUID, error) {
func (ctrl *V1Controller) routeUUID(r *http.Request, key string) (uuid.UUID, error) {
ID, err := uuid.Parse(chi.URLParam(r, key))
if err != nil {
return uuid.Nil, validate.NewInvalidRouteKeyError(key)
return uuid.Nil, validate.NewRouteKeyError(key)
}
return ID, nil
}

View File

@@ -1,35 +1,70 @@
package v1
import (
"context"
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
"github.com/rs/zerolog/log"
)
type EnsureAssetIDResult struct {
type ActionAmountResult struct {
Completed int `json:"completed"`
}
// HandleGroupInvitationsCreate godoc
// @Summary Get the current user
// @Tags Group
// @Produce json
// @Success 200 {object} EnsureAssetIDResult
// @Router /v1/actions/ensure-asset-ids [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc {
func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, error)) errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
totalCompleted, err := ctrl.svc.Items.EnsureAssetID(ctx, ctx.GID)
totalCompleted, err := fn(ctx, ctx.GID)
if err != nil {
log.Err(err).Msg("failed to ensure asset id")
log.Err(err).Str("action_ref", ref).Msg("failed to run action")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, EnsureAssetIDResult{Completed: totalCompleted})
return server.JSON(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
}
}
// HandleEnsureAssetID godoc
//
// @Summary Ensure Asset IDs
// @Description Ensures all items in the database have an asset ID
// @Tags Actions
// @Produce json
// @Success 200 {object} ActionAmountResult
// @Router /v1/actions/ensure-asset-ids [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleEnsureAssetID() errchain.HandlerFunc {
return actionHandlerFactory("ensure asset IDs", ctrl.svc.Items.EnsureAssetID)
}
// HandleEnsureImportRefs godoc
//
// @Summary Ensures Import Refs
// @Description Ensures all items in the database have an import ref
// @Tags Actions
// @Produce json
// @Success 200 {object} ActionAmountResult
// @Router /v1/actions/ensure-import-refs [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleEnsureImportRefs() errchain.HandlerFunc {
return actionHandlerFactory("ensure import refs", ctrl.svc.Items.EnsureImportRef)
}
// HandleItemDateZeroOut godoc
//
// @Summary Zero Out Time Fields
// @Description Resets all item date fields to the beginning of the day
// @Tags Actions
// @Produce json
// @Success 200 {object} ActionAmountResult
// @Router /v1/actions/zero-item-time-fields [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleItemDateZeroOut() errchain.HandlerFunc {
return actionHandlerFactory("zero out date time", ctrl.repo.Items.ZeroOutTimeFields)
}

View File

@@ -9,20 +9,22 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
"github.com/rs/zerolog/log"
)
// HandleItemGet godocs
// @Summary Gets an item by Asset ID
// @Tags Assets
// @Produce json
// @Param id path string true "Asset ID"
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/assets/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc {
// HandleAssetGet godocs
//
// @Summary Get Item by Asset ID
// @Tags Items
// @Produce json
// @Param id path string true "Asset ID"
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/assets/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
assetIdParam := chi.URLParam(r, "id")
@@ -37,7 +39,7 @@ func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc {
if pageParam != "" {
page, err = strconv.ParseInt(pageParam, 10, 64)
if err != nil {
return server.Respond(w, http.StatusBadRequest, "Invalid page number")
return server.JSON(w, http.StatusBadRequest, "Invalid page number")
}
}
@@ -46,16 +48,15 @@ func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc {
if pageSizeParam != "" {
pageSize, err = strconv.ParseInt(pageSizeParam, 10, 64)
if err != nil {
return server.Respond(w, http.StatusBadRequest, "Invalid page size")
return server.JSON(w, http.StatusBadRequest, "Invalid page size")
}
}
items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GID, repo.AssetID(assetId), int(page), int(pageSize))
if err != nil {
log.Err(err).Msg("failed to get item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, items)
return server.JSON(w, http.StatusOK, items)
}
}

View File

@@ -3,11 +3,13 @@ package v1
import (
"errors"
"net/http"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
"github.com/rs/zerolog/log"
)
@@ -19,42 +21,46 @@ type (
}
LoginForm struct {
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username"`
Password string `json:"password"`
StayLoggedIn bool `json:"stayLoggedIn"`
}
)
// HandleAuthLogin godoc
// @Summary User Login
// @Tags Authentication
// @Accept x-www-form-urlencoded
// @Accept application/json
// @Param username formData string false "string" example(admin@admin.com)
// @Param password formData string false "string" example(admin)
// @Produce json
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc {
//
// @Summary User Login
// @Tags Authentication
// @Accept x-www-form-urlencoded
// @Accept application/json
// @Param username formData string false "string" example(admin@admin.com)
// @Param password formData string false "string" example(admin)
// @Param payload body LoginForm true "Login Data"
// @Produce json
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
loginForm := &LoginForm{}
switch r.Header.Get("Content-Type") {
case server.ContentFormUrlEncoded:
case "application/x-www-form-urlencoded":
err := r.ParseForm()
if err != nil {
return server.Respond(w, http.StatusBadRequest, server.Wrap(err))
return errors.New("failed to parse form")
}
loginForm.Username = r.PostFormValue("username")
loginForm.Password = r.PostFormValue("password")
case server.ContentJSON:
loginForm.StayLoggedIn = r.PostFormValue("stayLoggedIn") == "true"
case "application/json":
err := server.Decode(r, loginForm)
if err != nil {
log.Err(err).Msg("failed to decode login form")
return errors.New("failed to decode login form")
}
default:
return server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
return server.JSON(w, http.StatusBadRequest, errors.New("invalid content type"))
}
if loginForm.Username == "" || loginForm.Password == "" {
@@ -70,13 +76,12 @@ func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc {
)
}
newToken, err := ctrl.svc.User.Login(r.Context(), loginForm.Username, loginForm.Password)
newToken, err := ctrl.svc.User.Login(r.Context(), strings.ToLower(loginForm.Username), loginForm.Password, loginForm.StayLoggedIn)
if err != nil {
return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, TokenResponse{
return server.JSON(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
AttachmentToken: newToken.AttachmentToken,
@@ -85,12 +90,13 @@ func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc {
}
// HandleAuthLogout godoc
// @Summary User Logout
// @Tags Authentication
// @Success 204
// @Router /v1/users/logout [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc {
//
// @Summary User Logout
// @Tags Authentication
// @Success 204
// @Router /v1/users/logout [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context())
if token == "" {
@@ -102,19 +108,20 @@ func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleAuthLogout godoc
// @Summary User Token Refresh
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
// @Description This does not validate that the user still exists within the database.
// @Tags Authentication
// @Success 200
// @Router /v1/users/refresh [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() server.HandlerFunc {
//
// @Summary User Token Refresh
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
// @Description This does not validate that the user still exists within the database.
// @Tags Authentication
// @Success 200
// @Router /v1/users/refresh [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
requestToken := services.UseTokenCtx(r.Context())
if requestToken == "" {
@@ -126,6 +133,6 @@ func (ctrl *V1Controller) HandleAuthRefresh() server.HandlerFunc {
return validate.NewUnauthorizedError()
}
return server.Respond(w, http.StatusOK, newToken)
return server.JSON(w, http.StatusOK, newToken)
}
}

View File

@@ -6,14 +6,13 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
)
type (
GroupInvitationCreate struct {
Uses int `json:"uses"`
Uses int `json:"uses" validate:"required,min=1,max=100"`
ExpiresAt time.Time `json:"expiresAt"`
}
@@ -25,93 +24,65 @@ type (
)
// HandleGroupGet godoc
// @Summary Get the current user's group
// @Tags Group
// @Produce json
// @Success 200 {object} repo.Group
// @Router /v1/groups [Get]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc {
return ctrl.handleGroupGeneral()
//
// @Summary Get Group
// @Tags Group
// @Produce json
// @Success 200 {object} repo.Group
// @Router /v1/groups [Get]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc {
fn := func(r *http.Request) (repo.Group, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.GroupByID(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupUpdate godoc
// @Summary Updates some fields of the current users group
// @Tags Group
// @Produce json
// @Param payload body repo.GroupUpdate true "User Data"
// @Success 200 {object} repo.Group
// @Router /v1/groups [Put]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc {
return ctrl.handleGroupGeneral()
}
func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
switch r.Method {
case http.MethodGet:
group, err := ctrl.repo.Groups.GroupByID(ctx, ctx.GID)
if err != nil {
log.Err(err).Msg("failed to get group")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, group)
case http.MethodPut:
data := repo.GroupUpdate{}
if err := server.Decode(r, &data); err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
group, err := ctrl.svc.Group.UpdateGroup(ctx, data)
if err != nil {
log.Err(err).Msg("failed to update group")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, group)
}
return nil
//
// @Summary Update Group
// @Tags Group
// @Produce json
// @Param payload body repo.GroupUpdate true "User Data"
// @Success 200 {object} repo.Group
// @Router /v1/groups [Put]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) {
auth := services.NewContext(r.Context())
return ctrl.svc.Group.UpdateGroup(auth, body)
}
return adapters.Action(fn, http.StatusOK)
}
// HandleGroupInvitationsCreate godoc
// @Summary Get the current user
// @Tags Group
// @Produce json
// @Param payload body GroupInvitationCreate true "User Data"
// @Success 200 {object} GroupInvitation
// @Router /v1/groups/invitations [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupInvitationsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
data := GroupInvitationCreate{}
if err := server.Decode(r, &data); err != nil {
log.Err(err).Msg("failed to decode user registration data")
return validate.NewRequestError(err, http.StatusBadRequest)
//
// @Summary Create Group Invitation
// @Tags Group
// @Produce json
// @Param payload body GroupInvitationCreate true "User Data"
// @Success 200 {object} GroupInvitation
// @Router /v1/groups/invitations [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupInvitationsCreate() errchain.HandlerFunc {
fn := func(r *http.Request, body GroupInvitationCreate) (GroupInvitation, error) {
if body.ExpiresAt.IsZero() {
body.ExpiresAt = time.Now().Add(time.Hour * 24)
}
if data.ExpiresAt.IsZero() {
data.ExpiresAt = time.Now().Add(time.Hour * 24)
}
auth := services.NewContext(r.Context())
ctx := services.NewContext(r.Context())
token, err := ctrl.svc.Group.NewInvitation(auth, body.Uses, body.ExpiresAt)
token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt)
if err != nil {
log.Err(err).Msg("failed to create new token")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, GroupInvitation{
return GroupInvitation{
Token: token,
ExpiresAt: data.ExpiresAt,
Uses: data.Uses,
})
ExpiresAt: body.ExpiresAt,
Uses: body.Uses,
}, err
}
return adapters.Action(fn, http.StatusCreated)
}

View File

@@ -2,176 +2,222 @@ package v1
import (
"database/sql"
"encoding/csv"
"errors"
"net/http"
"strings"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
"github.com/rs/zerolog/log"
)
// HandleItemsGetAll godoc
// @Summary Get 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"
// @Param labels query []string false "label Ids" collectionFormat(multi)
// @Param locations query []string false "location Ids" collectionFormat(multi)
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc {
//
// @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"
// @Param labels query []string false "label Ids" collectionFormat(multi)
// @Param locations query []string false "location Ids" collectionFormat(multi)
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
extractQuery := func(r *http.Request) repo.ItemQuery {
params := r.URL.Query()
return repo.ItemQuery{
filterFieldItems := func(raw []string) []repo.FieldQuery {
var items []repo.FieldQuery
for _, v := range raw {
parts := strings.SplitN(v, "=", 2)
if len(parts) == 2 {
items = append(items, repo.FieldQuery{
Name: parts[0],
Value: parts[1],
})
}
}
return items
}
v := repo.ItemQuery{
Page: queryIntOrNegativeOne(params.Get("page")),
PageSize: queryIntOrNegativeOne(params.Get("pageSize")),
Search: params.Get("q"),
LocationIDs: queryUUIDList(params, "locations"),
LabelIDs: queryUUIDList(params, "labels"),
IncludeArchived: queryBool(params.Get("includeArchived")),
Fields: filterFieldItems(params["fields"]),
}
if strings.HasPrefix(v.Search, "#") {
aidStr := strings.TrimPrefix(v.Search, "#")
aid, ok := repo.ParseAssetID(aidStr)
if ok {
v.Search = ""
v.AssetID = aid
}
}
return v
}
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return server.Respond(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{
return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{
Items: []repo.ItemSummary{},
})
}
log.Err(err).Msg("failed to get items")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, items)
return server.JSON(w, http.StatusOK, items)
}
}
// HandleItemsCreate godoc
// @Summary Create a new item
// @Tags Items
// @Produce json
// @Param payload body repo.ItemCreate true "Item Data"
// @Success 200 {object} repo.ItemSummary
// @Router /v1/items [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
createData := repo.ItemCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode request body")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
ctx := services.NewContext(r.Context())
item, err := ctrl.svc.Items.Create(ctx, createData)
if err != nil {
log.Err(err).Msg("failed to create item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, item)
//
// @Summary Create Item
// @Tags Items
// @Produce json
// @Param payload body repo.ItemCreate true "Item Data"
// @Success 201 {object} repo.ItemSummary
// @Router /v1/items [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() errchain.HandlerFunc {
fn := func(r *http.Request, body repo.ItemCreate) (repo.ItemOut, error) {
return ctrl.svc.Items.Create(services.NewContext(r.Context()), body)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleItemGet godocs
// @Summary Gets a item and fields
// @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() server.HandlerFunc {
return ctrl.handleItemsGeneral()
//
// @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 {
fn := func(r *http.Request, ID uuid.UUID) (repo.ItemOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleItemDelete godocs
// @Summary deletes a item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 204
// @Router /v1/items/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc {
return ctrl.handleItemsGeneral()
//
// @Summary Delete Item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 204
// @Router /v1/items/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.Items.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleItemUpdate godocs
// @Summary updates a item
// @Tags Items
// @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() server.HandlerFunc {
return ctrl.handleItemsGeneral()
//
// @Summary Update Item
// @Tags Items
// @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 {
fn := func(r *http.Request, ID uuid.UUID, body repo.ItemUpdate) (repo.ItemOut, error) {
auth := services.NewContext(r.Context())
body.ID = ID
return ctrl.repo.Items.UpdateByGroup(auth, auth.GID, body)
}
return adapters.ActionID("id", fn, http.StatusOK)
}
func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
items, err := ctrl.repo.Items.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to get item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, items)
case http.MethodDelete:
err = ctrl.repo.Items.DeleteByGroup(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
body := repo.ItemUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode request body")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.repo.Items.UpdateByGroup(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, result)
}
return nil
// HandleGetAllCustomFieldNames godocs
//
// @Summary Get All Custom Field Names
// @Tags Items
// @Produce json
// @Success 200
// @Router /v1/items/fields [GET]
// @Success 200 {object} []string
// @Security Bearer
func (ctrl *V1Controller) HandleGetAllCustomFieldNames() errchain.HandlerFunc {
fn := func(r *http.Request) ([]string, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGetAllCustomFieldValues godocs
//
// @Summary Get All Custom Field Values
// @Tags Items
// @Produce json
// @Success 200
// @Router /v1/items/fields/values [GET]
// @Success 200 {object} []string
// @Security Bearer
func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc {
type query struct {
Field string `schema:"field" validate:"required"`
}
fn := func(r *http.Request, q query) ([]string, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Items.GetAllCustomFieldValues(auth, auth.GID, q.Field)
}
return adapters.Action(fn, http.StatusOK)
}
// HandleItemsImport godocs
// @Summary imports items into the database
// @Tags Items
// @Produce json
// @Success 204
// @Param csv formData file true "Image to upload"
// @Router /v1/items/import [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc {
//
// @Summary Import Items
// @Tags Items
// @Produce json
// @Success 204
// @Param csv formData file true "Image to upload"
// @Router /v1/items/import [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil {
log.Err(err).Msg("failed to parse multipart form")
@@ -184,20 +230,40 @@ func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
data, err := services.ReadCsv(file)
if err != nil {
log.Err(err).Msg("failed to read csv")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, data)
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, file)
if err != nil {
log.Err(err).Msg("failed to import items")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleItemsExport godocs
//
// @Summary Export Items
// @Tags Items
// @Success 200 {string} string "text/csv"
// @Router /v1/items/export [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
csvData, err := ctrl.svc.Items.ExportTSV(r.Context(), ctx.GID)
if err != nil {
log.Err(err).Msg("failed to export items")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "text/tsv")
w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.tsv")
writer := csv.NewWriter(w)
writer.Comma = '\t'
return writer.WriteAll(csvData)
}
}

View File

@@ -8,7 +8,8 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
"github.com/rs/zerolog/log"
)
@@ -18,19 +19,20 @@ type (
}
)
// HandleItemsImport godocs
// @Summary imports items into the database
// @Tags Items Attachments
// @Produce json
// @Param id path string true "Item ID"
// @Param file formData file true "File attachment"
// @Param type formData string true "Type of file"
// @Param name formData string true "name of the file including extension"
// @Success 200 {object} repo.ItemOut
// @Failure 422 {object} server.ErrorResponse
// @Router /v1/items/{id}/attachments [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc {
// HandleItemAttachmentCreate godocs
//
// @Summary Create Item Attachment
// @Tags Items Attachments
// @Produce json
// @Param id path string true "Item ID"
// @Param file formData file true "File attachment"
// @Param type formData string true "Type of file"
// @Param name formData string true "name of the file including extension"
// @Success 200 {object} repo.ItemOut
// @Failure 422 {object} mid.ErrorResponse
// @Router /v1/items/{id}/attachments [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil {
@@ -60,7 +62,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc {
}
if !errs.Nil() {
return server.Respond(w, http.StatusUnprocessableEntity, errs)
return server.JSON(w, http.StatusUnprocessableEntity, errs)
}
attachmentType := r.FormValue("type")
@@ -82,51 +84,53 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc {
attachment.Type(attachmentType),
file,
)
if err != nil {
log.Err(err).Msg("failed to add attachment")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, item)
return server.JSON(w, http.StatusCreated, item)
}
}
// HandleItemAttachmentGet godocs
// @Summary retrieves an attachment for an item
// @Tags Items Attachments
// @Produce application/octet-stream
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 200 {object} ItemAttachmentToken
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentGet() server.HandlerFunc {
//
// @Summary Get Item Attachment
// @Tags Items Attachments
// @Produce application/octet-stream
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 200 {object} ItemAttachmentToken
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentGet() errchain.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentDelete godocs
// @Summary retrieves an attachment for an item
// @Tags Items Attachments
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 204
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDelete() server.HandlerFunc {
//
// @Summary Delete Item Attachment
// @Tags Items Attachments
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 204
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDelete() errchain.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentUpdate godocs
// @Summary retrieves an attachment for an item
// @Tags Items Attachments
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() server.HandlerFunc {
//
// @Summary Update Item Attachment
// @Tags Items Attachments
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() errchain.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
@@ -161,7 +165,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
return server.JSON(w, http.StatusNoContent, nil)
// Update Attachment Handler
case http.MethodPut:
@@ -179,7 +183,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, val)
return server.JSON(w, http.StatusOK, val)
}
return nil

View File

@@ -3,141 +3,100 @@ package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
)
// HandleLabelsGetAll godoc
// @Summary Get All Labels
// @Tags Labels
// @Produce json
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
// @Router /v1/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
user := services.UseUserCtx(r.Context())
labels, err := ctrl.repo.Labels.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("error getting labels")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, server.Results{Items: labels})
//
// @Summary Get All Labels
// @Tags Labels
// @Produce json
// @Success 200 {object} []repo.LabelOut
// @Router /v1/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.LabelSummary, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Labels.GetAll(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleLabelsCreate godoc
// @Summary Create a new label
// @Tags Labels
// @Produce json
// @Param payload body repo.LabelCreate true "Label Data"
// @Success 200 {object} repo.LabelSummary
// @Router /v1/labels [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
createData := repo.LabelCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("error decoding label create data")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
label, err := ctrl.repo.Labels.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("error creating label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, label)
//
// @Summary Create Label
// @Tags Labels
// @Produce json
// @Param payload body repo.LabelCreate true "Label Data"
// @Success 200 {object} repo.LabelSummary
// @Router /v1/labels [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc {
fn := func(r *http.Request, data repo.LabelCreate) (repo.LabelOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Labels.Create(auth, auth.GID, data)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleLabelDelete godocs
// @Summary deletes a label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 204
// @Router /v1/labels/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
//
// @Summary Delete Label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 204
// @Router /v1/labels/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleLabelGet godocs
// @Summary Gets a label and fields
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
//
// @Summary Get Label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.LabelOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Labels.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleLabelUpdate godocs
// @Summary updates a label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
func (ctrl *V1Controller) handleLabelsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
labels, err := ctrl.repo.Labels.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil {
if ent.IsNotFound(err) {
log.Err(err).
Str("id", ID.String()).
Msg("label not found")
return validate.NewRequestError(err, http.StatusNotFound)
}
log.Err(err).Msg("error getting label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, labels)
case http.MethodDelete:
err = ctrl.repo.Labels.DeleteByGroup(ctx, ctx.GID, ID)
if err != nil {
log.Err(err).Msg("error deleting label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
body := repo.LabelUpdate{}
if err := server.Decode(r, &body); err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.repo.Labels.UpdateByGroup(ctx, ctx.GID, body)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, result)
}
return nil
//
// @Summary Update Label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) {
auth := services.NewContext(r.Context())
data.ID = ID
return ctrl.repo.Labels.UpdateByGroup(auth, auth.GID, data)
}
return adapters.ActionID("id", fn, http.StatusOK)
}

View File

@@ -3,154 +3,120 @@ package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
)
// HandleLocationGetAll godoc
// @Summary Get All Locations
// @Tags Locations
// @Produce json
// @Param filterChildren query bool false "Filter locations with parents"
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
// @Router /v1/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
user := services.UseUserCtx(r.Context())
q := r.URL.Query()
filter := repo.LocationQuery{
FilterChildren: queryBool(q.Get("filterChildren")),
}
locations, err := ctrl.repo.Locations.GetAll(r.Context(), user.GroupID, filter)
if err != nil {
log.Err(err).Msg("failed to get locations")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, server.Results{Items: locations})
// HandleLocationTreeQuery
//
// @Summary Get Locations Tree
// @Tags Locations
// @Produce json
// @Param withItems query bool false "include items in response tree"
// @Success 200 {object} []repo.TreeItem
// @Router /v1/locations/tree [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc {
fn := func(r *http.Request, query repo.TreeQuery) ([]repo.TreeItem, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.Tree(auth, auth.GID, query)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleLocationCreate godoc
// @Summary Create a new location
// @Tags Locations
// @Produce json
// @Param payload body repo.LocationCreate true "Location Data"
// @Success 200 {object} repo.LocationSummary
// @Router /v1/locations [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
createData := repo.LocationCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode location create data")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
location, err := ctrl.repo.Locations.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("failed to create location")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, location)
// HandleLocationGetAll
//
// @Summary Get All Locations
// @Tags Locations
// @Produce json
// @Param filterChildren query bool false "Filter locations with parents"
// @Success 200 {object} []repo.LocationOutCount
// @Router /v1/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc {
fn := func(r *http.Request, q repo.LocationQuery) ([]repo.LocationOutCount, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.GetAll(auth, auth.GID, q)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleLocationDelete godocs
// @Summary deletes a location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 204
// @Router /v1/locations/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc {
return ctrl.handleLocationGeneral()
}
// HandleLocationGet godocs
// @Summary Gets a location and fields
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc {
return ctrl.handleLocationGeneral()
}
// HandleLocationUpdate godocs
// @Summary updates a location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Param payload body repo.LocationUpdate true "Location Data"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() server.HandlerFunc {
return ctrl.handleLocationGeneral()
}
func (ctrl *V1Controller) handleLocationGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
location, err := ctrl.repo.Locations.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil {
l := log.Err(err).
Str("ID", ID.String()).
Str("GID", ctx.GID.String())
if ent.IsNotFound(err) {
l.Msg("location not found")
return validate.NewRequestError(err, http.StatusNotFound)
}
l.Msg("failed to get location")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, location)
case http.MethodPut:
body := repo.LocationUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode location update data")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.repo.Locations.UpdateOneByGroup(r.Context(), ctx.GID, ID, body)
if err != nil {
log.Err(err).Msg("failed to update location")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, result)
case http.MethodDelete:
err = ctrl.repo.Locations.DeleteByGroup(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete location")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
}
return nil
// HandleLocationCreate
//
// @Summary Create Location
// @Tags Locations
// @Produce json
// @Param payload body repo.LocationCreate true "Location Data"
// @Success 200 {object} repo.LocationSummary
// @Router /v1/locations [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc {
fn := func(r *http.Request, createData repo.LocationCreate) (repo.LocationOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.Create(auth, auth.GID, createData)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleLocationDelete
//
// @Summary Delete Location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 204
// @Router /v1/locations/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleLocationGet
//
// @Summary Get Location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleLocationUpdate
//
// @Summary Update Location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Param payload body repo.LocationUpdate true "Location Data"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) {
auth := services.NewContext(r.Context())
body.ID = ID
return ctrl.repo.Locations.UpdateByGroup(auth, auth.GID, ID, body)
}
return adapters.ActionID("id", fn, http.StatusOK)
}

View File

@@ -3,123 +3,80 @@ package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
)
// HandleMaintenanceGetLog godoc
// @Summary Get Maintenance Log
// @Tags Maintenance
// @Produce json
// @Success 200 {object} repo.MaintenanceLog
// @Router /v1/items/{id}/maintenance [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc {
return ctrl.handleMaintenanceLog()
//
// @Summary Get Maintenance Log
// @Tags Maintenance
// @Produce json
// @Success 200 {object} repo.MaintenanceLog
// @Router /v1/items/{id}/maintenance [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q)
}
return adapters.QueryID("id", fn, http.StatusOK)
}
// HandleMaintenanceEntryCreate godoc
// @Summary Create Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc {
return ctrl.handleMaintenanceLog()
//
// @Summary Create Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
// @Success 201 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc {
fn := func(r *http.Request, itemID uuid.UUID, body repo.MaintenanceEntryCreate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.Create(auth, itemID, body)
}
return adapters.ActionID("id", fn, http.StatusCreated)
}
// HandleMaintenanceEntryDelete godoc
// @Summary Delete Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Success 204
// @Router /v1/items/{id}/maintenance/{entry_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc {
return ctrl.handleMaintenanceLog()
//
// @Summary Delete Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Success 204
// @Router /v1/items/{id}/maintenance/{entry_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.MaintEntry.Delete(auth, entryID)
return nil, err
}
return adapters.CommandID("entry_id", fn, http.StatusNoContent)
}
// HandleMaintenanceEntryUpdate godoc
// @Summary Update Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance/{entry_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() server.HandlerFunc {
return ctrl.handleMaintenanceLog()
}
func (ctrl *V1Controller) handleMaintenanceLog() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
itemID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
mlog, err := ctrl.repo.MaintEntry.GetLog(ctx, itemID)
if err != nil {
log.Err(err).Msg("failed to get items")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, mlog)
case http.MethodPost:
var create repo.MaintenanceEntryCreate
err := server.Decode(r, &create)
if err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
entry, err := ctrl.repo.MaintEntry.Create(ctx, itemID, create)
if err != nil {
log.Err(err).Msg("failed to create item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, entry)
case http.MethodPut:
entryID, err := ctrl.routeUUID(r, "entry_id")
if err != nil {
return err
}
var update repo.MaintenanceEntryUpdate
err = server.Decode(r, &update)
if err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
entry, err := ctrl.repo.MaintEntry.Update(ctx, entryID, update)
if err != nil {
log.Err(err).Msg("failed to update item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, entry)
case http.MethodDelete:
entryID, err := ctrl.routeUUID(r, "entry_id")
if err != nil {
return err
}
err = ctrl.repo.MaintEntry.Delete(ctx, entryID)
if err != nil {
log.Err(err).Msg("failed to delete item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
}
return nil
//
// @Summary Update Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance/{entry_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.Update(auth, entryID, body)
}
return adapters.ActionID("entry_id", fn, http.StatusOK)
}

View File

@@ -0,0 +1,105 @@
package v1
import (
"net/http"
"github.com/containrrr/shoutrrr"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
)
// HandleGetUserNotifiers godoc
//
// @Summary Get Notifiers
// @Tags Notifiers
// @Produce json
// @Success 200 {object} []repo.NotifierOut
// @Router /v1/notifiers [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc {
fn := func(r *http.Request, _ struct{}) ([]repo.NotifierOut, error) {
user := services.UseUserCtx(r.Context())
return ctrl.repo.Notifiers.GetByUser(r.Context(), user.ID)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleCreateNotifier godoc
//
// @Summary Create Notifier
// @Tags Notifiers
// @Produce json
// @Param payload body repo.NotifierCreate true "Notifier Data"
// @Success 200 {object} repo.NotifierOut
// @Router /v1/notifiers [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc {
fn := func(r *http.Request, in repo.NotifierCreate) (repo.NotifierOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Notifiers.Create(auth, auth.GID, auth.UID, in)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleDeleteNotifier godocs
//
// @Summary Delete a Notifier
// @Tags Notifiers
// @Param id path string true "Notifier ID"
// @Success 204
// @Router /v1/notifiers/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
return nil, ctrl.repo.Notifiers.Delete(auth, auth.UID, ID)
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleUpdateNotifier godocs
//
// @Summary Update Notifier
// @Tags Notifiers
// @Param id path string true "Notifier ID"
// @Param payload body repo.NotifierUpdate true "Notifier Data"
// @Success 200 {object} repo.NotifierOut
// @Router /v1/notifiers/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Notifiers.Update(auth, auth.UID, ID, in)
}
return adapters.ActionID("id", fn, http.StatusOK)
}
// HandlerNotifierTest godoc
//
// @Summary Test Notifier
// @Tags Notifiers
// @Produce json
// @Param id path string true "Notifier ID"
// @Param url query string true "URL"
// @Success 204
// @Router /v1/notifiers/test [POST]
// @Security Bearer
func (ctrl *V1Controller) HandlerNotifierTest() errchain.HandlerFunc {
type body struct {
URL string `json:"url" validate:"required"`
}
fn := func(r *http.Request, q body) (any, error) {
err := shoutrrr.Send(q.URL, "Test message from Homebox")
return nil, err
}
return adapters.Action(fn, http.StatusOK)
}

View File

@@ -6,8 +6,8 @@ import (
"io"
"net/http"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
@@ -19,32 +19,31 @@ var qrcodeLogo []byte
// HandleGenerateQRCode godoc
//
// @Summary Encode data into QRCode
// @Summary Create QR Code
// @Tags Items
// @Produce json
// @Param data query string false "data to be encoded into qrcode"
// @Success 200 {string} string "image/jpeg"
// @Router /v1/qrcode [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGenerateQRCode() server.HandlerFunc {
const MaxLength = 4_296 // assume alphanumeric characters only
func (ctrl *V1Controller) HandleGenerateQRCode() errchain.HandlerFunc {
type query struct {
// 4,296 characters is the maximum length of a QR code
Data string `schema:"data" validate:"required,max=4296"`
}
return func(w http.ResponseWriter, r *http.Request) error {
data := r.URL.Query().Get("data")
q, err := adapters.DecodeQuery[query](r)
if err != nil {
return err
}
image, err := png.Decode(bytes.NewReader(qrcodeLogo))
if err != nil {
panic(err)
}
if len(data) > MaxLength {
return validate.NewFieldErrors(validate.FieldError{
Field: "data",
Error: "max length is 4,296 characters exceeded",
})
}
qrc, err := qrcode.New(data)
qrc, err := qrcode.New(q.Data)
if err != nil {
return err
}

View File

@@ -0,0 +1,32 @@
package v1
import (
"net/http"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/safeserve/errchain"
)
// HandleBillOfMaterialsExport godoc
//
// @Summary Export Bill of Materials
// @Tags Reporting
// @Produce json
// @Success 200 {string} string "text/csv"
// @Router /v1/reporting/bill-of-materials [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleBillOfMaterialsExport() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
actor := services.UseUserCtx(r.Context())
csv, err := ctrl.svc.Items.ExportBillOfMaterialsTSV(r.Context(), actor.GroupID)
if err != nil {
return err
}
w.Header().Set("Content-Type", "text/tsv")
w.Header().Set("Content-Disposition", "attachment; filename=bill-of-materials.tsv")
_, err = w.Write(csv)
return err
}
}

View File

@@ -5,80 +5,75 @@ import (
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
)
// HandleGroupGet godoc
// @Summary Get the current user's group statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
stats, err := ctrl.repo.Groups.StatsLocationsByPurchasePrice(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
//
// @Summary Get Location Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupGet godoc
// @Summary Get the current user's group statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
stats, err := ctrl.repo.Groups.StatsLabelsByPurchasePrice(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
// HandleGroupStatisticsLabels godoc
//
// @Summary Get Label Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupGet godoc
// @Summary Get the current user's group statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} repo.GroupStatistics
// @Router /v1/groups/statistics [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
stats, err := ctrl.repo.Groups.StatsGroup(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
// HandleGroupStatistics godoc
//
// @Summary Get Group Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} repo.GroupStatistics
// @Router /v1/groups/statistics [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc {
fn := func(r *http.Request) (repo.GroupStatistics, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsGroup(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupGet godoc
// @Summary Queries the changes overtime of the purchase price over time
// @Tags Statistics
// @Produce json
// @Success 200 {object} repo.ValueOverTime
// @Param start query string false "start date"
// @Param end query string false "end date"
// @Router /v1/groups/statistics/purchase-price [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() server.HandlerFunc {
// HandleGroupStatisticsPriceOverTime godoc
//
// @Summary Get Purchase Price Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} repo.ValueOverTime
// @Param start query string false "start date"
// @Param end query string false "end date"
// @Router /v1/groups/statistics/purchase-price [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerFunc {
parseDate := func(datestr string, defaultDate time.Time) (time.Time, error) {
if datestr == "" {
return defaultDate, nil
@@ -104,6 +99,6 @@ func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() server.HandlerFun
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
return server.JSON(w, http.StatusOK, stats)
}
}

View File

@@ -1,24 +1,27 @@
package v1
import (
"fmt"
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
"github.com/rs/zerolog/log"
)
// HandleUserSelf godoc
// @Summary Get the current user
// @Tags User
// @Produce json
// @Param payload body services.UserRegistration true "User Data"
// @Success 204
// @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc {
// HandleUserRegistration godoc
//
// @Summary Register New User
// @Tags User
// @Produce json
// @Param payload body services.UserRegistration true "User Data"
// @Success 204
// @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
regData := services.UserRegistration{}
@@ -28,7 +31,7 @@ func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc {
}
if !ctrl.allowRegistration && regData.GroupToken == "" {
return validate.NewRequestError(nil, http.StatusForbidden)
return validate.NewRequestError(fmt.Errorf("user registration disabled"), http.StatusForbidden)
}
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
@@ -37,18 +40,19 @@ func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleUserSelf godoc
// @Summary Get the current user
// @Tags User
// @Produce json
// @Success 200 {object} server.Result{item=repo.UserOut}
// @Router /v1/users/self [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc {
//
// @Summary Get User Self
// @Tags User
// @Produce json
// @Success 200 {object} Wrapped{item=repo.UserOut}
// @Router /v1/users/self [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelf() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context())
usr, err := ctrl.svc.User.GetSelf(r.Context(), token)
@@ -57,19 +61,20 @@ func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, server.Wrap(usr))
return server.JSON(w, http.StatusOK, Wrap(usr))
}
}
// HandleUserSelfUpdate godoc
// @Summary Update the current user
// @Tags User
// @Produce json
// @Param payload body repo.UserUpdate true "User Data"
// @Success 200 {object} server.Result{item=repo.UserUpdate}
// @Router /v1/users/self [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc {
//
// @Summary Update Account
// @Tags User
// @Produce json
// @Param payload body repo.UserUpdate true "User Data"
// @Success 200 {object} Wrapped{item=repo.UserUpdate}
// @Router /v1/users/self [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfUpdate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
updateData := repo.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil {
@@ -79,23 +84,23 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc {
actor := services.UseUserCtx(r.Context())
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, server.Wrap(newData))
return server.JSON(w, http.StatusOK, Wrap(newData))
}
}
// HandleUserSelfDelete godoc
// @Summary Deletes the user account
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() server.HandlerFunc {
//
// @Summary Delete Account
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden)
@@ -106,7 +111,7 @@ func (ctrl *V1Controller) HandleUserSelfDelete() server.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
return server.JSON(w, http.StatusNoContent, nil)
}
}
@@ -118,13 +123,14 @@ type (
)
// HandleUserSelfChangePassword godoc
// @Summary Updates the users password
// @Tags User
// @Success 204
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfChangePassword() server.HandlerFunc {
//
// @Summary Change Password
// @Tags User
// @Success 204
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden)
@@ -143,6 +149,6 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() server.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
return server.JSON(w, http.StatusNoContent, nil)
}
}

View File

@@ -2,6 +2,7 @@ package main
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
@@ -9,6 +10,9 @@ import (
atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql/schema"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/hay-kot/homebox/backend/app/api/static/docs"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent"
@@ -16,9 +20,13 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/homebox/backend/pkgs/server"
_ "github.com/mattn/go-sqlite3"
"github.com/hay-kot/safeserve/errchain"
"github.com/hay-kot/safeserve/server"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
_ "github.com/hay-kot/homebox/backend/pkgs/cgofreesqlite"
)
var (
@@ -27,9 +35,9 @@ var (
buildTime = "now"
)
// @title Go API Templates
// @title Homebox API
// @version 1.0
// @description This is a simple Rest API Server Template that implements some basic User and Authentication patterns to help you get started and bootstrap your next project!.
// @description Track, Manage, and Organize your Things.
// @contact.name Don't
// @license.name MIT
// @BasePath /api
@@ -38,6 +46,8 @@ var (
// @name Authorization
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
func main() {
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
cfg, err := config.New()
if err != nil {
panic(err)
@@ -57,7 +67,7 @@ func run(cfg *config.Config) error {
// =========================================================================
// Initialize Database & Repos
err := os.MkdirAll(cfg.Storage.Data, 0755)
err := os.MkdirAll(cfg.Storage.Data, 0o755)
if err != nil {
log.Fatal().Err(err).Msg("failed to create data directory")
}
@@ -118,26 +128,27 @@ func run(cfg *config.Config) error {
)
// =========================================================================
// Start Server\
// Start Server
logger := log.With().Caller().Logger()
mwLogger := mid.Logger(logger)
if app.conf.Mode == config.ModeDevelopment {
mwLogger = mid.SugarLogger(logger)
}
router := chi.NewMux()
router.Use(
middleware.RequestID,
middleware.RealIP,
mid.Logger(logger),
middleware.Recoverer,
middleware.StripSlashes,
)
chain := errchain.New(mid.Errors(app.server, logger))
app.mountRoutes(router, chain, app.repos)
app.server = server.NewServer(
server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port),
server.WithMiddleware(
mwLogger,
mid.Errors(logger),
mid.Panic(app.conf.Mode == config.ModeDevelopment),
),
)
app.mountRoutes(app.repos)
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
// =========================================================================
@@ -159,6 +170,19 @@ func run(cfg *config.Config) error {
Msg("failed to purge expired invitations")
}
})
go app.startBgTask(time.Duration(1)*time.Hour, func() {
now := time.Now()
if now.Hour() == 8 {
fmt.Println("run notifiers")
err := app.services.BackgroundService.SendNotifiersToday(context.Background())
if err != nil {
log.Error().
Err(err).
Msg("failed to send notifiers")
}
}
})
// TODO: Remove through external API that does setup
if cfg.Demo {
@@ -175,5 +199,5 @@ func run(cfg *config.Config) error {
}()
}
return app.server.Start()
return app.server.Start(router)
}

View File

@@ -4,20 +4,19 @@ import (
"context"
"errors"
"net/http"
"net/url"
"strings"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
)
type tokenHasKey struct {
key string
}
var (
hashedToken = tokenHasKey{key: "hashedToken"}
)
var hashedToken = tokenHasKey{key: "hashedToken"}
type RoleMode int
@@ -31,9 +30,9 @@ const (
// the required roles, a 403 Forbidden will be returned.
//
// WARNING: This middleware _MUST_ be called after mwAuthToken or else it will panic
func (a *app) mwRoles(rm RoleMode, required ...string) server.Middleware {
return func(next server.Handler) server.Handler {
return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
func (a *app) mwRoles(rm RoleMode, required ...string) errchain.Middleware {
return func(next errchain.Handler) errchain.Handler {
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
maybeToken := ctx.Value(hashedToken)
@@ -70,6 +69,45 @@ func (a *app) mwRoles(rm RoleMode, required ...string) server.Middleware {
}
}
type KeyFunc func(r *http.Request) (string, error)
func getBearer(r *http.Request) (string, error) {
auth := r.Header.Get("Authorization")
if auth == "" {
return "", errors.New("authorization header is required")
}
return auth, nil
}
func getQuery(r *http.Request) (string, error) {
token := r.URL.Query().Get("access_token")
if token == "" {
return "", errors.New("access_token query is required")
}
token, err := url.QueryUnescape(token)
if err != nil {
return "", errors.New("access_token query is required")
}
return token, nil
}
func getCookie(r *http.Request) (string, error) {
cookie, err := r.Cookie("hb.auth.token")
if err != nil {
return "", errors.New("access_token cookie is required")
}
token, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", errors.New("access_token cookie is required")
}
return token, nil
}
// mwAuthToken is a middleware that will check the database for a stateful token
// and attach it's user to the request context, or return an appropriate error.
// Authorization support is by token via Headers or Query Parameter
@@ -77,26 +115,36 @@ func (a *app) mwRoles(rm RoleMode, required ...string) server.Middleware {
// Example:
// - header = "Bearer 1234567890"
// - query = "?access_token=1234567890"
func (a *app) mwAuthToken(next server.Handler) server.Handler {
return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
requestToken := r.Header.Get("Authorization")
if requestToken == "" {
// check for query param
requestToken = r.URL.Query().Get("access_token")
if requestToken == "" {
return validate.NewRequestError(errors.New("Authorization header or query is required"), http.StatusUnauthorized)
// - cookie = hb.auth.token = 1234567890
func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler {
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
keyFuncs := [...]KeyFunc{
getBearer,
getCookie,
getQuery,
}
var requestToken string
for _, keyFunc := range keyFuncs {
token, err := keyFunc(r)
if err == nil {
requestToken = token
break
}
}
if requestToken == "" {
return validate.NewRequestError(errors.New("Authorization header or query is required"), http.StatusUnauthorized)
}
requestToken = strings.TrimPrefix(requestToken, "Bearer ")
r = r.WithContext(context.WithValue(r.Context(), hashedToken, requestToken))
usr, err := a.services.User.GetSelf(r.Context(), requestToken)
// Check the database for the token
if err != nil {
return validate.NewRequestError(errors.New("Authorization header is required"), http.StatusUnauthorized)
return validate.NewRequestError(errors.New("valid authorization header is required"), http.StatusUnauthorized)
}
r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken))

View File

@@ -10,12 +10,13 @@ import (
"path"
"path/filepath"
"github.com/go-chi/chi/v5"
"github.com/hay-kot/homebox/backend/app/api/handlers/debughandlers"
v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1"
_ "github.com/hay-kot/homebox/backend/app/api/static/docs"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/safeserve/errchain"
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
)
@@ -36,12 +37,12 @@ func (a *app) debugRouter() *http.ServeMux {
}
// registerRoutes registers all the routes for the API
func (a *app) mountRoutes(repos *repo.AllRepos) {
func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllRepos) {
registerMimes()
a.server.Get("/swagger/*", server.ToHandler(httpSwagger.Handler(
r.Get("/swagger/*", httpSwagger.Handler(
httpSwagger.URL(fmt.Sprintf("%s://%s/swagger/doc.json", a.conf.Swagger.Scheme, a.conf.Swagger.Host)),
)))
))
// =========================================================================
// API Version 1
@@ -56,82 +57,103 @@ func (a *app) mountRoutes(repos *repo.AllRepos) {
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
)
a.server.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
r.Get(v1Base("/status"), chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: version,
Commit: commit,
BuildTime: buildTime,
}))
})))
a.server.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
a.server.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin()))
userMW := []server.Middleware{
userMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
}
a.server.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf(), userMW...)
a.server.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate(), userMW...)
a.server.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete(), userMW...)
a.server.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout(), userMW...)
a.server.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh(), userMW...)
a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), userMW...)
r.Get(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
r.Put(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
r.Delete(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), userMW...)
a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), userMW...)
a.server.Get(v1Base("/groups/statistics/purchase-price"), v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...)
a.server.Get(v1Base("/groups/statistics/locations"), v1Ctrl.HandleGroupStatisticsLocations(), userMW...)
a.server.Get(v1Base("/groups/statistics/labels"), v1Ctrl.HandleGroupStatisticsLabels(), userMW...)
r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
r.Get(v1Base("/groups/statistics/purchase-price"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
r.Get(v1Base("/groups/statistics/locations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
r.Get(v1Base("/groups/statistics/labels"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
// TODO: I don't like /groups being the URL for users
a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), userMW...)
a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), userMW...)
r.Get(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
r.Put(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), userMW...)
r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), userMW...)
a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), userMW...)
a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), userMW...)
a.server.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate(), userMW...)
a.server.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete(), userMW...)
r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
r.Get(v1Base("/locations/tree"), chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
r.Get(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...))
r.Put(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
r.Delete(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
a.server.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll(), userMW...)
a.server.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate(), userMW...)
a.server.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet(), userMW...)
a.server.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate(), userMW...)
a.server.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete(), userMW...)
r.Get(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
r.Post(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
r.Get(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
r.Put(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
r.Delete(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), userMW...)
a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), userMW...)
a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), userMW...)
a.server.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet(), userMW...)
a.server.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate(), userMW...)
a.server.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete(), userMW...)
r.Get(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
r.Post(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
r.Post(v1Base("/items/import"), chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
r.Get(v1Base("/items/export"), chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...))
r.Get(v1Base("/items/fields"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...))
r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
a.server.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate(), userMW...)
a.server.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate(), userMW...)
a.server.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete(), userMW...)
r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...))
r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...))
r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
a.server.Get(v1Base("/items/{id}/maintenance"), v1Ctrl.HandleMaintenanceEntryCreate(), userMW...)
a.server.Post(v1Base("/items/{id}/maintenance"), v1Ctrl.HandleMaintenanceEntryCreate(), userMW...)
a.server.Put(v1Base("/items/{id}/maintenance/{entry_id}"), v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...)
a.server.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), v1Ctrl.HandleMaintenanceEntryDelete(), userMW...)
r.Post(v1Base("/items/{id}/attachments"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
a.server.Get(v1Base("/asset/{id}"), v1Ctrl.HandleAssetGet(), userMW...)
r.Get(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
r.Post(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
r.Put(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
r.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
r.Get(v1Base("/asset/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
// Notifiers
r.Get(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
r.Post(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))
r.Put(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...))
r.Delete(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...))
r.Post(v1Base("/notifiers/test"), chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...))
// Asset-Like endpoints
a.server.Get(
assetMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
}
r.Get(
v1Base("/qrcode"),
v1Ctrl.HandleGenerateQRCode(),
a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...),
)
a.server.Get(
r.Get(
v1Base("/items/{id}/attachments/{attachment_id}"),
v1Ctrl.HandleItemAttachmentGet(),
a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
)
a.server.NotFound(notFoundHandler())
// Reporting Services
r.Get(v1Base("/reporting/bill-of-materials"), chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
r.NotFound(chain.ToHandlerFunc(notFoundHandler()))
}
func registerMimes() {
@@ -148,7 +170,7 @@ func registerMimes() {
// notFoundHandler perform the main logic around handling the internal SPA embed and ensuring that
// the client side routing is handled correctly.
func notFoundHandler() server.HandlerFunc {
func notFoundHandler() errchain.HandlerFunc {
tryRead := func(fs embed.FS, prefix, requestedPath string, w http.ResponseWriter) error {
f, err := fs.Open(path.Join(prefix, requestedPath))
if err != nil {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,14 @@
basePath: /api
definitions:
mid.ErrorResponse:
properties:
error:
type: string
fields:
additionalProperties:
type: string
type: object
type: object
repo.DocumentOut:
properties:
id:
@@ -67,6 +76,7 @@ definitions:
repo.ItemCreate:
properties:
description:
maxLength: 1000
type: string
labelIds:
items:
@@ -76,10 +86,14 @@ definitions:
description: Edges
type: string
name:
maxLength: 255
minLength: 1
type: string
parentId:
type: string
x-nullable: true
required:
- name
type: object
repo.ItemField:
properties:
@@ -93,8 +107,6 @@ definitions:
type: integer
textValue:
type: string
timeValue:
type: string
type:
type: string
type: object
@@ -275,6 +287,7 @@ definitions:
warrantyDetails:
type: string
warrantyExpires:
description: Sold
type: string
type: object
repo.LabelCreate:
@@ -282,9 +295,14 @@ definitions:
color:
type: string
description:
maxLength: 255
type: string
name:
maxLength: 255
minLength: 1
type: string
required:
- name
type: object
repo.LabelOut:
properties:
@@ -322,6 +340,9 @@ definitions:
type: string
name:
type: string
parentId:
type: string
x-nullable: true
type: object
repo.LocationOut:
properties:
@@ -388,41 +409,55 @@ definitions:
type: object
repo.MaintenanceEntry:
properties:
completedDate:
description: Sold
type: string
cost:
example: "0"
type: string
date:
type: string
description:
type: string
id:
type: string
name:
type: string
scheduledDate:
description: Sold
type: string
type: object
repo.MaintenanceEntryCreate:
properties:
completedDate:
description: Sold
type: string
cost:
example: "0"
type: string
date:
type: string
description:
type: string
name:
type: string
scheduledDate:
description: Sold
type: string
required:
- name
type: object
repo.MaintenanceEntryUpdate:
properties:
completedDate:
description: Sold
type: string
cost:
example: "0"
type: string
date:
type: string
description:
type: string
name:
type: string
scheduledDate:
description: Sold
type: string
type: object
repo.MaintenanceLog:
properties:
@@ -437,6 +472,51 @@ definitions:
itemId:
type: string
type: object
repo.NotifierCreate:
properties:
isActive:
type: boolean
name:
maxLength: 255
minLength: 1
type: string
url:
type: string
required:
- name
- url
type: object
repo.NotifierOut:
properties:
createdAt:
type: string
groupId:
type: string
id:
type: string
isActive:
type: boolean
name:
type: string
updatedAt:
type: string
userId:
type: string
type: object
repo.NotifierUpdate:
properties:
isActive:
type: boolean
name:
maxLength: 255
minLength: 1
type: string
url:
type: string
x-nullable: true
required:
- name
type: object
repo.PaginationResult-repo_ItemSummary:
properties:
items:
@@ -459,6 +539,19 @@ definitions:
total:
type: number
type: object
repo.TreeItem:
properties:
children:
items:
$ref: '#/definitions/repo.TreeItem'
type: array
id:
type: string
name:
type: string
type:
type: string
type: object
repo.UserOut:
properties:
email:
@@ -507,28 +600,6 @@ definitions:
value:
type: number
type: object
server.ErrorResponse:
properties:
error:
type: string
fields:
additionalProperties:
type: string
type: object
type: object
server.Result:
properties:
details: {}
error:
type: boolean
item: {}
message:
type: string
type: object
server.Results:
properties:
items: {}
type: object
services.UserRegistration:
properties:
email:
@@ -540,8 +611,15 @@ definitions:
token:
type: string
type: object
v1.ActionAmountResult:
properties:
completed:
type: integer
type: object
v1.ApiSummary:
properties:
allowRegistration:
type: boolean
build:
$ref: '#/definitions/v1.Build'
demo:
@@ -573,11 +651,6 @@ definitions:
new:
type: string
type: object
v1.EnsureAssetIDResult:
properties:
completed:
type: integer
type: object
v1.GroupInvitation:
properties:
expiresAt:
@@ -592,13 +665,26 @@ definitions:
expiresAt:
type: string
uses:
maximum: 100
minimum: 1
type: integer
required:
- uses
type: object
v1.ItemAttachmentToken:
properties:
token:
type: string
type: object
v1.LoginForm:
properties:
password:
type: string
stayLoggedIn:
type: boolean
username:
type: string
type: object
v1.TokenResponse:
properties:
attachmentToken:
@@ -608,31 +694,64 @@ definitions:
token:
type: string
type: object
v1.Wrapped:
properties:
item: {}
type: object
info:
contact:
name: Don't
description: This is a simple Rest API Server Template that implements some basic
User and Authentication patterns to help you get started and bootstrap your next
project!.
description: Track, Manage, and Organize your Things.
license:
name: MIT
title: Go API Templates
title: Homebox API
version: "1.0"
paths:
/v1/actions/ensure-asset-ids:
post:
description: Ensures all items in the database have an asset ID
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/v1.EnsureAssetIDResult'
$ref: '#/definitions/v1.ActionAmountResult'
security:
- Bearer: []
summary: Get the current user
summary: Ensure Asset IDs
tags:
- Group
- Actions
/v1/actions/ensure-import-refs:
post:
description: Ensures all items in the database have an import ref
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/v1.ActionAmountResult'
security:
- Bearer: []
summary: Ensures Import Refs
tags:
- Actions
/v1/actions/zero-item-time-fields:
post:
description: Resets all item date fields to the beginning of the day
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/v1.ActionAmountResult'
security:
- Bearer: []
summary: Zero Out Time Fields
tags:
- Actions
/v1/assets/{id}:
get:
parameters:
@@ -650,9 +769,9 @@ paths:
$ref: '#/definitions/repo.PaginationResult-repo_ItemSummary'
security:
- Bearer: []
summary: Gets an item by Asset ID
summary: Get Item by Asset ID
tags:
- Assets
- Items
/v1/groups:
get:
produces:
@@ -664,7 +783,7 @@ paths:
$ref: '#/definitions/repo.Group'
security:
- Bearer: []
summary: Get the current user's group
summary: Get Group
tags:
- Group
put:
@@ -684,7 +803,7 @@ paths:
$ref: '#/definitions/repo.Group'
security:
- Bearer: []
summary: Updates some fields of the current users group
summary: Update Group
tags:
- Group
/v1/groups/invitations:
@@ -705,7 +824,7 @@ paths:
$ref: '#/definitions/v1.GroupInvitation'
security:
- Bearer: []
summary: Get the current user
summary: Create Group Invitation
tags:
- Group
/v1/groups/statistics:
@@ -719,7 +838,7 @@ paths:
$ref: '#/definitions/repo.GroupStatistics'
security:
- Bearer: []
summary: Get the current user's group statistics
summary: Get Group Statistics
tags:
- Statistics
/v1/groups/statistics/labels:
@@ -735,7 +854,7 @@ paths:
type: array
security:
- Bearer: []
summary: Get the current user's group statistics
summary: Get Label Statistics
tags:
- Statistics
/v1/groups/statistics/locations:
@@ -751,7 +870,7 @@ paths:
type: array
security:
- Bearer: []
summary: Get the current user's group statistics
summary: Get Location Statistics
tags:
- Statistics
/v1/groups/statistics/purchase-price:
@@ -774,7 +893,7 @@ paths:
$ref: '#/definitions/repo.ValueOverTime'
security:
- Bearer: []
summary: Queries the changes overtime of the purchase price over time
summary: Get Purchase Price Statistics
tags:
- Statistics
/v1/items:
@@ -815,7 +934,7 @@ paths:
$ref: '#/definitions/repo.PaginationResult-repo_ItemSummary'
security:
- Bearer: []
summary: Get All Items
summary: Query All Items
tags:
- Items
post:
@@ -829,13 +948,13 @@ paths:
produces:
- application/json
responses:
"200":
description: OK
"201":
description: Created
schema:
$ref: '#/definitions/repo.ItemSummary'
security:
- Bearer: []
summary: Create a new item
summary: Create Item
tags:
- Items
/v1/items/{id}:
@@ -853,7 +972,7 @@ paths:
description: No Content
security:
- Bearer: []
summary: deletes a item
summary: Delete Item
tags:
- Items
get:
@@ -872,7 +991,7 @@ paths:
$ref: '#/definitions/repo.ItemOut'
security:
- Bearer: []
summary: Gets a item and fields
summary: Get Item
tags:
- Items
put:
@@ -897,7 +1016,7 @@ paths:
$ref: '#/definitions/repo.ItemOut'
security:
- Bearer: []
summary: updates a item
summary: Update Item
tags:
- Items
/v1/items/{id}/attachments:
@@ -933,10 +1052,10 @@ paths:
"422":
description: Unprocessable Entity
schema:
$ref: '#/definitions/server.ErrorResponse'
$ref: '#/definitions/mid.ErrorResponse'
security:
- Bearer: []
summary: imports items into the database
summary: Create Item Attachment
tags:
- Items Attachments
/v1/items/{id}/attachments/{attachment_id}:
@@ -957,7 +1076,7 @@ paths:
description: No Content
security:
- Bearer: []
summary: retrieves an attachment for an item
summary: Delete Item Attachment
tags:
- Items Attachments
get:
@@ -981,7 +1100,7 @@ paths:
$ref: '#/definitions/v1.ItemAttachmentToken'
security:
- Bearer: []
summary: retrieves an attachment for an item
summary: Get Item Attachment
tags:
- Items Attachments
put:
@@ -1009,7 +1128,7 @@ paths:
$ref: '#/definitions/repo.ItemOut'
security:
- Bearer: []
summary: retrieves an attachment for an item
summary: Update Item Attachment
tags:
- Items Attachments
/v1/items/{id}/maintenance:
@@ -1037,8 +1156,8 @@ paths:
produces:
- application/json
responses:
"200":
description: OK
"201":
description: Created
schema:
$ref: '#/definitions/repo.MaintenanceEntry'
security:
@@ -1078,6 +1197,50 @@ paths:
summary: Update Maintenance Entry
tags:
- Maintenance
/v1/items/export:
get:
responses:
"200":
description: text/csv
schema:
type: string
security:
- Bearer: []
summary: Export Items
tags:
- Items
/v1/items/fields:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
security:
- Bearer: []
summary: Get All Custom Field Names
tags:
- Items
/v1/items/fields/values:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
security:
- Bearer: []
summary: Get All Custom Field Values
tags:
- Items
/v1/items/import:
post:
parameters:
@@ -1093,7 +1256,7 @@ paths:
description: No Content
security:
- Bearer: []
summary: imports items into the database
summary: Import Items
tags:
- Items
/v1/labels:
@@ -1104,14 +1267,9 @@ paths:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Results'
- properties:
items:
items:
$ref: '#/definitions/repo.LabelOut'
type: array
type: object
items:
$ref: '#/definitions/repo.LabelOut'
type: array
security:
- Bearer: []
summary: Get All Labels
@@ -1134,7 +1292,7 @@ paths:
$ref: '#/definitions/repo.LabelSummary'
security:
- Bearer: []
summary: Create a new label
summary: Create Label
tags:
- Labels
/v1/labels/{id}:
@@ -1152,7 +1310,7 @@ paths:
description: No Content
security:
- Bearer: []
summary: deletes a label
summary: Delete Label
tags:
- Labels
get:
@@ -1171,7 +1329,7 @@ paths:
$ref: '#/definitions/repo.LabelOut'
security:
- Bearer: []
summary: Gets a label and fields
summary: Get Label
tags:
- Labels
put:
@@ -1190,7 +1348,7 @@ paths:
$ref: '#/definitions/repo.LabelOut'
security:
- Bearer: []
summary: updates a label
summary: Update Label
tags:
- Labels
/v1/locations:
@@ -1206,14 +1364,9 @@ paths:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Results'
- properties:
items:
items:
$ref: '#/definitions/repo.LocationOutCount'
type: array
type: object
items:
$ref: '#/definitions/repo.LocationOutCount'
type: array
security:
- Bearer: []
summary: Get All Locations
@@ -1236,7 +1389,7 @@ paths:
$ref: '#/definitions/repo.LocationSummary'
security:
- Bearer: []
summary: Create a new location
summary: Create Location
tags:
- Locations
/v1/locations/{id}:
@@ -1254,7 +1407,7 @@ paths:
description: No Content
security:
- Bearer: []
summary: deletes a location
summary: Delete Location
tags:
- Locations
get:
@@ -1273,7 +1426,7 @@ paths:
$ref: '#/definitions/repo.LocationOut'
security:
- Bearer: []
summary: Gets a location and fields
summary: Get Location
tags:
- Locations
put:
@@ -1298,9 +1451,128 @@ paths:
$ref: '#/definitions/repo.LocationOut'
security:
- Bearer: []
summary: updates a location
summary: Update Location
tags:
- Locations
/v1/locations/tree:
get:
parameters:
- description: include items in response tree
in: query
name: withItems
type: boolean
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.TreeItem'
type: array
security:
- Bearer: []
summary: Get Locations Tree
tags:
- Locations
/v1/notifiers:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.NotifierOut'
type: array
security:
- Bearer: []
summary: Get Notifiers
tags:
- Notifiers
post:
parameters:
- description: Notifier Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.NotifierCreate'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.NotifierOut'
security:
- Bearer: []
summary: Create Notifier
tags:
- Notifiers
/v1/notifiers/{id}:
delete:
parameters:
- description: Notifier ID
in: path
name: id
required: true
type: string
responses:
"204":
description: No Content
security:
- Bearer: []
summary: Delete a Notifier
tags:
- Notifiers
put:
parameters:
- description: Notifier ID
in: path
name: id
required: true
type: string
- description: Notifier Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.NotifierUpdate'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.NotifierOut'
security:
- Bearer: []
summary: Update Notifier
tags:
- Notifiers
/v1/notifiers/test:
post:
parameters:
- description: Notifier ID
in: path
name: id
required: true
type: string
- description: URL
in: query
name: url
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
security:
- Bearer: []
summary: Test Notifier
tags:
- Notifiers
/v1/qrcode:
get:
parameters:
@@ -1317,9 +1589,23 @@ paths:
type: string
security:
- Bearer: []
summary: Encode data into QRCode
summary: Create QR Code
tags:
- Items
/v1/reporting/bill-of-materials:
get:
produces:
- application/json
responses:
"200":
description: text/csv
schema:
type: string
security:
- Bearer: []
summary: Export Bill of Materials
tags:
- Reporting
/v1/status:
get:
produces:
@@ -1329,7 +1615,7 @@ paths:
description: OK
schema:
$ref: '#/definitions/v1.ApiSummary'
summary: Retrieves the basic information about the API
summary: Application Info
tags:
- Base
/v1/users/change-password:
@@ -1346,7 +1632,7 @@ paths:
description: No Content
security:
- Bearer: []
summary: Updates the users password
summary: Change Password
tags:
- User
/v1/users/login:
@@ -1365,6 +1651,12 @@ paths:
in: formData
name: password
type: string
- description: Login Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/v1.LoginForm'
produces:
- application/json
responses:
@@ -1412,7 +1704,7 @@ paths:
responses:
"204":
description: No Content
summary: Get the current user
summary: Register New User
tags:
- User
/v1/users/self:
@@ -1424,7 +1716,7 @@ paths:
description: No Content
security:
- Bearer: []
summary: Deletes the user account
summary: Delete Account
tags:
- User
get:
@@ -1435,14 +1727,14 @@ paths:
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Result'
- $ref: '#/definitions/v1.Wrapped'
- properties:
item:
$ref: '#/definitions/repo.UserOut'
type: object
security:
- Bearer: []
summary: Get the current user
summary: Get User Self
tags:
- User
put:
@@ -1460,14 +1752,14 @@ paths:
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Result'
- $ref: '#/definitions/v1.Wrapped'
- properties:
item:
$ref: '#/definitions/repo.UserUpdate'
type: object
security:
- Bearer: []
summary: Update the current user
summary: Update Account
tags:
- User
securityDefinitions:

View File

@@ -0,0 +1,80 @@
package main
import (
"fmt"
"os"
"regexp"
)
type ReReplace struct {
Regex *regexp.Regexp
Text string
}
func NewReReplace(regex string, replace string) ReReplace {
return ReReplace{
Regex: regexp.MustCompile(regex),
Text: replace,
}
}
func NewReDate(dateStr string) ReReplace {
return ReReplace{
Regex: regexp.MustCompile(fmt.Sprintf(`%s: string`, dateStr)),
Text: fmt.Sprintf(`%s: Date | string`, dateStr),
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Please provide a file path as an argument")
os.Exit(1)
}
path := os.Args[1]
fmt.Printf("Processing %s\n", path)
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("File %s does not exist\n", path)
os.Exit(1)
}
text := "/* post-processed by ./scripts/process-types.go */\n"
data, err := os.ReadFile(path)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
text += string(data)
replaces := [...]ReReplace{
NewReReplace(` Repo`, " "),
NewReReplace(` PaginationResultRepo`, " PaginationResult"),
NewReReplace(` Services`, " "),
NewReReplace(` V1`, " "),
NewReReplace(`\?:`, ":"),
NewReDate("createdAt"),
NewReDate("updatedAt"),
NewReDate("soldTime"),
NewReDate("purchaseTime"),
NewReDate("warrantyExpires"),
NewReDate("expiresAt"),
NewReDate("date"),
NewReDate("completedDate"),
NewReDate("scheduledDate"),
}
for _, replace := range replaces {
fmt.Printf("Replacing '%v' -> '%s'\n", replace.Regex, replace.Text)
text = replace.Regex.ReplaceAllString(text, replace.Text)
}
err = os.WriteFile(path, []byte(text), 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}

View File

@@ -1,22 +1,28 @@
module github.com/hay-kot/homebox/backend
go 1.19
go 1.20
require (
ariga.io/atlas v0.9.0
entgo.io/ent v0.11.5
github.com/ardanlabs/conf/v2 v2.2.0
ariga.io/atlas v0.10.0
entgo.io/ent v0.11.10
github.com/ardanlabs/conf/v3 v3.1.5
github.com/containrrr/shoutrrr v0.7.1
github.com/go-chi/chi/v5 v5.0.8
github.com/go-playground/validator/v10 v10.11.1
github.com/go-playground/validator/v10 v10.12.0
github.com/gocarina/gocsv v0.0.0-20230226133904-70c27cb2918a
github.com/google/uuid v1.3.0
github.com/gorilla/schema v1.2.0
github.com/hay-kot/safeserve v0.0.1
github.com/mattn/go-sqlite3 v1.14.16
github.com/rs/zerolog v1.28.0
github.com/stretchr/testify v1.8.1
github.com/swaggo/http-swagger v1.3.3
github.com/swaggo/swag v1.8.9
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.29.0
github.com/stretchr/testify v1.8.2
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.8.11
github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1
golang.org/x/crypto v0.5.0
golang.org/x/crypto v0.7.0
modernc.org/sqlite v1.21.0
)
require (
@@ -24,33 +30,45 @@ require (
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.7 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/hashicorp/hcl/v2 v2.15.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/leodido/go-urn v1.2.2 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/swaggo/files v1.0.0 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/zclconf/go-cty v1.12.1 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/tools v0.4.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.6.1-0.20230222164832-25d2519c8696 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.3 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,14 @@
package services
import "github.com/hay-kot/homebox/backend/internal/data/repo"
import (
"github.com/hay-kot/homebox/backend/internal/data/repo"
)
type AllServices struct {
User *UserService
Group *GroupService
Items *ItemService
User *UserService
Group *GroupService
Items *ItemService
BackgroundService *BackgroundService
}
type OptionsFunc func(*options)
@@ -40,5 +43,6 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
repo: repos,
autoIncrementAssetID: options.autoIncrementAssetID,
},
BackgroundService: &BackgroundService{repos},
}
}

View File

@@ -3,10 +3,8 @@ package services
import (
"context"
"log"
"math/rand"
"os"
"testing"
"time"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo"
@@ -49,8 +47,6 @@ func bootstrap() {
}
func TestMain(m *testing.M) {
rand.Seed(int64(time.Now().Unix()))
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)

View File

@@ -0,0 +1,5 @@
HB.location,HB.name,HB.quantity,HB.description,HB.field.Custom Field 1,HB.field.Custom Field 2,HB.field.Custom Field 3
loc,Item 1,1,Description 1,Value 1[1],Value 1[2],Value 1[3]
loc,Item 2,2,Description 2,Value 2[1],Value 2[2],Value 2[3]
loc,Item 3,3,Description 3,Value 3[1],Value 3[2],Value 3[3]
1 HB.location HB.name HB.quantity HB.description HB.field.Custom Field 1 HB.field.Custom Field 2 HB.field.Custom Field 3
2 loc Item 1 1 Description 1 Value 1[1] Value 1[2] Value 1[3]
3 loc Item 2 2 Description 2 Value 2[1] Value 2[2] Value 2[3]
4 loc Item 3 3 Description 3 Value 3[1] Value 3[2] Value 3[3]

View File

@@ -0,0 +1,4 @@
HB.location,HB.name,HB.quantity,HB.description
loc,Item 1,1,Description 1
loc,Item 2,2,Description 2
loc,Item 3,3,Description 3
1 HB.location HB.name HB.quantity HB.description
2 loc Item 1 1 Description 1
3 loc Item 2 2 Description 2
4 loc Item 3 3 Description 3

View File

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

View File

@@ -0,0 +1,42 @@
package reporting
import (
"github.com/gocarina/gocsv"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/data/types"
)
// =================================================================================================
type BillOfMaterialsEntry struct {
PurchaseDate types.Date `csv:"Purchase Date"`
Name string `csv:"Name"`
Description string `csv:"Description"`
Manufacturer string `csv:"Manufacturer"`
SerialNumber string `csv:"Serial Number"`
ModelNumber string `csv:"Model Number"`
Quantity int `csv:"Quantity"`
Price float64 `csv:"Price"`
TotalPrice float64 `csv:"Total Price"`
}
// BillOfMaterialsTSV returns a byte slice of the Bill of Materials for a given GID in TSV format
// See BillOfMaterialsEntry for the format of the output
func BillOfMaterialsTSV(entities []repo.ItemOut) ([]byte, error) {
bomEntries := make([]BillOfMaterialsEntry, len(entities))
for i, entity := range entities {
bomEntries[i] = BillOfMaterialsEntry{
PurchaseDate: entity.PurchaseTime,
Name: entity.Name,
Description: entity.Description,
Manufacturer: entity.Manufacturer,
SerialNumber: entity.SerialNumber,
ModelNumber: entity.ModelNumber,
Quantity: entity.Quantity,
Price: entity.PurchasePrice,
TotalPrice: entity.PurchasePrice * float64(entity.Quantity),
}
}
return gocsv.MarshalBytes(&bomEntries)
}

View File

@@ -0,0 +1,93 @@
package reporting
import (
"bytes"
"encoding/csv"
"errors"
"io"
"strings"
)
var (
ErrNoHomeboxHeaders = errors.New("no headers found")
ErrMissingRequiredHeaders = errors.New("missing required headers `HB.location` or `HB.name`")
)
// determineSeparator determines the separator used in the CSV file
// It returns the separator as a rune and an error if it could not be determined
//
// It is assumed that the first row is the header row and that the separator is the same
// for all rows.
//
// Supported separators are `,` and `\t`
func determineSeparator(data []byte) (rune, error) {
// First row
firstRow := bytes.Split(data, []byte("\n"))[0]
// find first comma or /t
comma := bytes.IndexByte(firstRow, ',')
tab := bytes.IndexByte(firstRow, '\t')
switch {
case comma == -1 && tab == -1:
return 0, errors.New("could not determine separator")
case tab > comma:
return '\t', nil
default:
return ',', nil
}
}
// readRawCsv reads a CSV file and returns the raw data as a 2D string array
// It determines the separator used in the CSV file and returns an error if
// it could not be determined
func readRawCsv(r io.Reader) ([][]string, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
reader := csv.NewReader(bytes.NewReader(data))
// Determine separator
sep, err := determineSeparator(data)
if err != nil {
return nil, err
}
reader.Comma = sep
return reader.ReadAll()
}
// parseHeaders parses the homebox headers from the CSV file and returns a map of the headers
// and their column index as well as a list of the field headers (HB.field.*) in the order
// they appear in the CSV file
//
// It returns an error if no homebox headers are found
func parseHeaders(headers []string) (hbHeaders map[string]int, fieldHeaders []string, err error) {
hbHeaders = map[string]int{} // initialize map
for col, h := range headers {
if strings.HasPrefix(h, "HB.field.") {
fieldHeaders = append(fieldHeaders, h)
}
if strings.HasPrefix(h, "HB.") {
hbHeaders[h] = col
}
}
required := []string{"HB.location", "HB.name"}
for _, h := range required {
if _, ok := hbHeaders[h]; !ok {
return nil, nil, ErrMissingRequiredHeaders
}
}
if len(hbHeaders) == 0 {
return nil, nil, ErrNoHomeboxHeaders
}
return hbHeaders, fieldHeaders, nil
}

View File

@@ -0,0 +1,85 @@
package reporting
import (
"strings"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/data/types"
)
type ExportItemFields struct {
Name string
Value string
}
type ExportTSVRow struct {
ImportRef string `csv:"HB.import_ref"`
Location LocationString `csv:"HB.location"`
LabelStr LabelString `csv:"HB.labels"`
AssetID repo.AssetID `csv:"HB.asset_id"`
Archived bool `csv:"HB.archived"`
Name string `csv:"HB.name"`
Quantity int `csv:"HB.quantity"`
Description string `csv:"HB.description"`
Insured bool `csv:"HB.insured"`
Notes string `csv:"HB.notes"`
PurchasePrice float64 `csv:"HB.purchase_price"`
PurchaseFrom string `csv:"HB.purchase_from"`
PurchaseTime types.Date `csv:"HB.purchase_time"`
Manufacturer string `csv:"HB.manufacturer"`
ModelNumber string `csv:"HB.model_number"`
SerialNumber string `csv:"HB.serial_number"`
LifetimeWarranty bool `csv:"HB.lifetime_warranty"`
WarrantyExpires types.Date `csv:"HB.warranty_expires"`
WarrantyDetails string `csv:"HB.warranty_details"`
SoldTo string `csv:"HB.sold_to"`
SoldPrice float64 `csv:"HB.sold_price"`
SoldTime types.Date `csv:"HB.sold_time"`
SoldNotes string `csv:"HB.sold_notes"`
Fields []ExportItemFields `csv:"-"`
}
// ============================================================================
// LabelString is a string slice that is used to represent a list of labels.
//
// For example, a list of labels "Important; Work" would be represented as a
// LabelString with the following values:
//
// LabelString{"Important", "Work"}
type LabelString []string
func parseLabelString(s string) LabelString {
v, _ := parseSeparatedString(s, ";")
return v
}
func (ls LabelString) String() string {
return strings.Join(ls, "; ")
}
// ============================================================================
// LocationString is a string slice that is used to represent a location
// hierarchy.
//
// For example, a location hierarchy of "Home / Bedroom / Desk" would be
// represented as a LocationString with the following values:
//
// LocationString{"Home", "Bedroom", "Desk"}
type LocationString []string
func parseLocationString(s string) LocationString {
v, _ := parseSeparatedString(s, "/")
return v
}
func (csf LocationString) String() string {
return strings.Join(csf, " / ")
}

View File

@@ -0,0 +1,310 @@
package reporting
import (
"fmt"
"io"
"reflect"
"sort"
"strconv"
"strings"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/data/types"
"github.com/rs/zerolog/log"
)
// IOSheet is the representation of a CSV/TSV sheet that is used for importing/exporting
// items from homebox. It is used to read/write the data from/to a CSV/TSV file given
// the standard format of the file.
//
// See ExportTSVRow for the format of the data in the sheet.
type IOSheet struct {
headers []string
custom []int
index map[string]int
Rows []ExportTSVRow
}
func (s *IOSheet) indexHeaders() {
s.index = make(map[string]int)
for i, h := range s.headers {
if strings.HasPrefix(h, "HB.field") {
s.custom = append(s.custom, i)
}
if strings.HasPrefix(h, "HB.") {
s.index[h] = i
}
}
}
func (s *IOSheet) GetColumn(str string) (col int, ok bool) {
if s.index == nil {
s.indexHeaders()
}
col, ok = s.index[str]
return
}
// Read reads a CSV/TSV and populates the "Rows" field with the data from the sheet
// Custom Fields are supported via the `HB.field.*` headers. The `HB.field.*` the "Name"
// of the field is the part after the `HB.field.` prefix. Additionally, Custom Fields with
// no value are excluded from the row.Fields slice, this includes empty strings.
//
// Note That
// - the first row is assumed to be the header
// - at least 1 row of data is required
// - rows and columns must be rectangular (i.e. all rows must have the same number of columns)
func (s *IOSheet) Read(data io.Reader) error {
sheet, err := readRawCsv(data)
if err != nil {
return err
}
if len(sheet) < 2 {
return fmt.Errorf("sheet must have at least 1 row of data (header + 1)")
}
s.headers = sheet[0]
s.Rows = make([]ExportTSVRow, len(sheet)-1)
for i, row := range sheet[1:] {
if len(row) != len(s.headers) {
return fmt.Errorf("row has %d columns, expected %d", len(row), len(s.headers))
}
rowData := ExportTSVRow{}
st := reflect.TypeOf(ExportTSVRow{})
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
tag := field.Tag.Get("csv")
if tag == "" || tag == "-" {
continue
}
col, ok := s.GetColumn(tag)
if !ok {
continue
}
val := row[col]
var v interface{}
switch field.Type {
case reflect.TypeOf(""):
v = val
case reflect.TypeOf(int(0)):
v = parseInt(val)
case reflect.TypeOf(bool(false)):
v = parseBool(val)
case reflect.TypeOf(float64(0)):
v = parseFloat(val)
// Custom Types
case reflect.TypeOf(types.Date{}):
v = types.DateFromString(val)
case reflect.TypeOf(repo.AssetID(0)):
v, _ = repo.ParseAssetID(val)
case reflect.TypeOf(LocationString{}):
v = parseLocationString(val)
case reflect.TypeOf(LabelString{}):
v = parseLabelString(val)
}
log.Debug().
Str("tag", tag).
Interface("val", v).
Str("type", fmt.Sprintf("%T", v)).
Msg("parsed value")
// Nil values are not allowed at the moment. This may change.
if v == nil {
return fmt.Errorf("could not convert %q to %s", val, field.Type)
}
ptrField := reflect.ValueOf(&rowData).Elem().Field(i)
ptrField.Set(reflect.ValueOf(v))
}
for _, col := range s.custom {
colName := strings.TrimPrefix(s.headers[col], "HB.field.")
customVal := row[col]
if customVal == "" {
continue
}
rowData.Fields = append(rowData.Fields, ExportItemFields{
Name: colName,
Value: customVal,
})
}
s.Rows[i] = rowData
}
return nil
}
// Write writes the sheet to a writer.
func (s *IOSheet) ReadItems(items []repo.ItemOut) {
s.Rows = make([]ExportTSVRow, len(items))
extraHeaders := map[string]struct{}{}
for i := range items {
item := items[i]
// TODO: Support fetching nested locations
locString := LocationString{item.Location.Name}
labelString := make([]string, len(item.Labels))
for i, l := range item.Labels {
labelString[i] = l.Name
}
customFields := make([]ExportItemFields, len(item.Fields))
for i, f := range item.Fields {
extraHeaders[f.Name] = struct{}{}
customFields[i] = ExportItemFields{
Name: f.Name,
Value: f.TextValue,
}
}
s.Rows[i] = ExportTSVRow{
// fill struct
Location: locString,
LabelStr: labelString,
ImportRef: item.ImportRef,
AssetID: item.AssetID,
Name: item.Name,
Quantity: item.Quantity,
Description: item.Description,
Insured: item.Insured,
Archived: item.Archived,
PurchasePrice: item.PurchasePrice,
PurchaseFrom: item.PurchaseFrom,
PurchaseTime: item.PurchaseTime,
Manufacturer: item.Manufacturer,
ModelNumber: item.ModelNumber,
SerialNumber: item.SerialNumber,
LifetimeWarranty: item.LifetimeWarranty,
WarrantyExpires: item.WarrantyExpires,
WarrantyDetails: item.WarrantyDetails,
SoldTo: item.SoldTo,
SoldTime: item.SoldTime,
SoldPrice: item.SoldPrice,
SoldNotes: item.SoldNotes,
Fields: customFields,
}
}
// Extract and sort additional headers for deterministic output
customHeaders := make([]string, 0, len(extraHeaders))
for k := range extraHeaders {
customHeaders = append(customHeaders, k)
}
sort.Strings(customHeaders)
st := reflect.TypeOf(ExportTSVRow{})
// Write headers
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
tag := field.Tag.Get("csv")
if tag == "" || tag == "-" {
continue
}
s.headers = append(s.headers, tag)
}
for _, h := range customHeaders {
s.headers = append(s.headers, "HB.field."+h)
}
}
// Writes the current sheet to a writer in TSV format.
func (s *IOSheet) TSV() ([][]string, error) {
memcsv := make([][]string, len(s.Rows)+1)
memcsv[0] = s.headers
// use struct tags in rows to dertmine column order
for i, row := range s.Rows {
rowIdx := i + 1
memcsv[rowIdx] = make([]string, len(s.headers))
st := reflect.TypeOf(row)
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
tag := field.Tag.Get("csv")
if tag == "" || tag == "-" {
continue
}
col, ok := s.GetColumn(tag)
if !ok {
continue
}
val := reflect.ValueOf(row).Field(i)
var v string
switch field.Type {
case reflect.TypeOf(""):
v = val.String()
case reflect.TypeOf(int(0)):
v = strconv.Itoa(int(val.Int()))
case reflect.TypeOf(bool(false)):
v = strconv.FormatBool(val.Bool())
case reflect.TypeOf(float64(0)):
v = strconv.FormatFloat(val.Float(), 'f', -1, 64)
// Custom Types
case reflect.TypeOf(types.Date{}):
v = val.Interface().(types.Date).String()
case reflect.TypeOf(repo.AssetID(0)):
v = val.Interface().(repo.AssetID).String()
case reflect.TypeOf(LocationString{}):
v = val.Interface().(LocationString).String()
case reflect.TypeOf(LabelString{}):
v = val.Interface().(LabelString).String()
default:
log.Debug().Str("type", field.Type.String()).Msg("unknown type")
}
memcsv[rowIdx][col] = v
}
for _, f := range row.Fields {
col, ok := s.GetColumn("HB.field." + f.Name)
if !ok {
continue
}
memcsv[i+1][col] = f.Value
}
}
return memcsv, nil
}

View File

@@ -0,0 +1,226 @@
package reporting
import (
"bytes"
"reflect"
"testing"
_ "embed"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert"
)
var (
//go:embed .testdata/import/minimal.csv
minimalImportCSV []byte
//go:embed .testdata/import/fields.csv
customFieldImportCSV []byte
//go:embed .testdata/import/types.csv
customTypesImportCSV []byte
//go:embed .testdata/import.csv
CSVData_Comma []byte
//go:embed .testdata/import.tsv
CSVData_Tab []byte
)
func TestSheet_Read(t *testing.T) {
tests := []struct {
name string
data []byte
want []ExportTSVRow
wantErr bool
}{
{
name: "minimal import",
data: minimalImportCSV,
want: []ExportTSVRow{
{Location: LocationString{"loc"}, Name: "Item 1", Quantity: 1, Description: "Description 1"},
{Location: LocationString{"loc"}, Name: "Item 2", Quantity: 2, Description: "Description 2"},
{Location: LocationString{"loc"}, Name: "Item 3", Quantity: 3, Description: "Description 3"},
},
},
{
name: "custom field import",
data: customFieldImportCSV,
want: []ExportTSVRow{
{
Location: LocationString{"loc"}, Name: "Item 1", Quantity: 1, Description: "Description 1",
Fields: []ExportItemFields{
{Name: "Custom Field 1", Value: "Value 1[1]"},
{Name: "Custom Field 2", Value: "Value 1[2]"},
{Name: "Custom Field 3", Value: "Value 1[3]"},
},
},
{
Location: LocationString{"loc"}, Name: "Item 2", Quantity: 2, Description: "Description 2",
Fields: []ExportItemFields{
{Name: "Custom Field 1", Value: "Value 2[1]"},
{Name: "Custom Field 2", Value: "Value 2[2]"},
{Name: "Custom Field 3", Value: "Value 2[3]"},
},
},
{
Location: LocationString{"loc"}, Name: "Item 3", Quantity: 3, Description: "Description 3",
Fields: []ExportItemFields{
{Name: "Custom Field 1", Value: "Value 3[1]"},
{Name: "Custom Field 2", Value: "Value 3[2]"},
{Name: "Custom Field 3", Value: "Value 3[3]"},
},
},
},
},
{
name: "custom types import",
data: customTypesImportCSV,
want: []ExportTSVRow{
{
Name: "Item 1",
AssetID: repo.AssetID(1),
Location: LocationString{"Path", "To", "Location 1"},
LabelStr: LabelString{"L1", "L2", "L3"},
},
{
Name: "Item 2",
AssetID: repo.AssetID(2),
Location: LocationString{"Path", "To", "Location 2"},
LabelStr: LabelString{"L1", "L2", "L3"},
},
{
Name: "Item 3",
AssetID: repo.AssetID(1000003),
Location: LocationString{"Path", "To", "Location 3"},
LabelStr: LabelString{"L1", "L2", "L3"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reader := bytes.NewReader(tt.data)
sheet := &IOSheet{}
err := sheet.Read(reader)
switch {
case tt.wantErr:
assert.Error(t, err)
default:
assert.NoError(t, err)
assert.ElementsMatch(t, tt.want, sheet.Rows)
}
})
}
}
func Test_parseHeaders(t *testing.T) {
tests := []struct {
name string
rawHeaders []string
wantHbHeaders map[string]int
wantFieldHeaders []string
wantErr bool
}{
{
name: "no hombox headers",
rawHeaders: []string{"Header 1", "Header 2", "Header 3"},
wantHbHeaders: nil,
wantFieldHeaders: nil,
wantErr: true,
},
{
name: "field headers only",
rawHeaders: []string{"HB.location", "HB.name", "HB.field.1", "HB.field.2", "HB.field.3"},
wantHbHeaders: map[string]int{
"HB.location": 0,
"HB.name": 1,
"HB.field.1": 2,
"HB.field.2": 3,
"HB.field.3": 4,
},
wantFieldHeaders: []string{"HB.field.1", "HB.field.2", "HB.field.3"},
wantErr: false,
},
{
name: "mixed headers",
rawHeaders: []string{"Header 1", "HB.name", "Header 2", "HB.field.2", "Header 3", "HB.field.3", "HB.location"},
wantHbHeaders: map[string]int{
"HB.name": 1,
"HB.field.2": 3,
"HB.field.3": 5,
"HB.location": 6,
},
wantFieldHeaders: []string{"HB.field.2", "HB.field.3"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotHbHeaders, gotFieldHeaders, err := parseHeaders(tt.rawHeaders)
if (err != nil) != tt.wantErr {
t.Errorf("parseHeaders() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotHbHeaders, tt.wantHbHeaders) {
t.Errorf("parseHeaders() gotHbHeaders = %v, want %v", gotHbHeaders, tt.wantHbHeaders)
}
if !reflect.DeepEqual(gotFieldHeaders, tt.wantFieldHeaders) {
t.Errorf("parseHeaders() gotFieldHeaders = %v, want %v", gotFieldHeaders, tt.wantFieldHeaders)
}
})
}
}
func Test_determineSeparator(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
want rune
wantErr bool
}{
{
name: "comma",
args: args{
data: CSVData_Comma,
},
want: ',',
wantErr: false,
},
{
name: "tab",
args: args{
data: CSVData_Tab,
},
want: '\t',
wantErr: false,
},
{
name: "invalid",
args: args{
data: []byte("a;b;c"),
},
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := determineSeparator(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("determineSeparator() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("determineSeparator() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,38 @@
package reporting
import (
"strconv"
"strings"
)
func parseSeparatedString(s string, sep string) ([]string, error) {
list := strings.Split(s, sep)
csf := make([]string, 0, len(list))
for _, s := range list {
trimmed := strings.TrimSpace(s)
if trimmed != "" {
csf = append(csf, trimmed)
}
}
return csf, nil
}
func parseFloat(s string) float64 {
if s == "" {
return 0
}
f, _ := strconv.ParseFloat(s, 64)
return f
}
func parseBool(s string) bool {
b, _ := strconv.ParseBool(s)
return b
}
func parseInt(s string) int {
i, _ := strconv.Atoi(s)
return i
}

View File

@@ -0,0 +1,65 @@
package reporting
import (
"reflect"
"testing"
)
func Test_parseSeparatedString(t *testing.T) {
type args struct {
s string
sep string
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{
name: "comma",
args: args{
s: "a,b,c",
sep: ",",
},
want: []string{"a", "b", "c"},
wantErr: false,
},
{
name: "trimmed comma",
args: args{
s: "a, b, c",
sep: ",",
},
want: []string{"a", "b", "c"},
},
{
name: "excessive whitespace",
args: args{
s: " a, b, c ",
sep: ",",
},
want: []string{"a", "b", "c"},
},
{
name: "empty",
args: args{
s: "",
sep: ",",
},
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseSeparatedString(tt.args.s, tt.args.sep)
if (err != nil) != tt.wantErr {
t.Errorf("parseSeparatedString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseSeparatedString() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,81 @@
package services
import (
"context"
"strings"
"time"
"github.com/containrrr/shoutrrr"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/data/types"
"github.com/rs/zerolog/log"
)
type BackgroundService struct {
repos *repo.AllRepos
}
func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
// Get All Groups
groups, err := svc.repos.Groups.GetAllGroups(ctx)
if err != nil {
return err
}
today := types.DateFromTime(time.Now())
for i := range groups {
group := groups[i]
entries, err := svc.repos.MaintEntry.GetScheduled(ctx, group.ID, today)
if err != nil {
return err
}
if len(entries) == 0 {
log.Debug().
Str("group_name", group.Name).
Str("group_id", group.ID.String()).
Msg("No scheduled maintenance for today")
continue
}
notifiers, err := svc.repos.Notifiers.GetByGroup(ctx, group.ID)
if err != nil {
return err
}
urls := make([]string, len(notifiers))
for i := range notifiers {
urls[i] = notifiers[i].URL
}
bldr := strings.Builder{}
bldr.WriteString("Homebox Maintenance for (")
bldr.WriteString(today.String())
bldr.WriteString("):\n")
for i := range entries {
entry := entries[i]
bldr.WriteString(" - ")
bldr.WriteString(entry.Name)
bldr.WriteString("\n")
}
var sendErrs []error
for i := range urls {
err := shoutrrr.Send(urls[i], bldr.String())
if err != nil {
sendErrs = append(sendErrs, err)
}
}
if len(sendErrs) > 0 {
return sendErrs[0]
}
}
return nil
}

View File

@@ -3,10 +3,13 @@ package services
import (
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/rs/zerolog/log"
)
var (
@@ -37,7 +40,6 @@ func (svc *ItemService) Create(ctx Context, item repo.ItemCreate) (repo.ItemOut,
func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int, error) {
items, err := svc.repo.Items.GetAllZeroAssetID(ctx, GID)
if err != nil {
return 0, err
}
@@ -61,190 +63,290 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int,
return finished, nil
}
func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data [][]string) (int, error) {
loaded := []csvRow{}
// Skip first row
for _, row := range data[1:] {
// Skip empty rows
if len(row) == 0 {
continue
}
if len(row) != NumOfCols {
return 0, ErrInvalidCsv
}
r := newCsvRow(row)
loaded = append(loaded, r)
}
// validate rows
var errMap = map[int][]error{}
var hasErr bool
for i, r := range loaded {
errs := r.validate()
if len(errs) > 0 {
hasErr = true
lineNum := i + 2
errMap[lineNum] = errs
}
}
if hasErr {
for lineNum, errs := range errMap {
for _, err := range errs {
log.Error().Err(err).Int("line", lineNum).Msg("csv import error")
}
}
}
// Bootstrap the locations and labels so we can reuse the created IDs for the items
locations := map[string]uuid.UUID{}
existingLocation, err := svc.repo.Locations.GetAll(ctx, GID, repo.LocationQuery{})
func (svc *ItemService) EnsureImportRef(ctx context.Context, GID uuid.UUID) (int, error) {
ids, err := svc.repo.Items.GetAllZeroImportRef(ctx, GID)
if err != nil {
return 0, err
}
for _, loc := range existingLocation {
locations[loc.Name] = loc.ID
finished := 0
for _, itemID := range ids {
ref := uuid.New().String()[0:8]
err = svc.repo.Items.Patch(ctx, GID, itemID, repo.ItemPatch{ImportRef: &ref})
if err != nil {
return 0, err
}
finished++
}
labels := map[string]uuid.UUID{}
existingLabels, err := svc.repo.Labels.GetAll(ctx, GID)
return finished, nil
}
func serializeLocation[T ~[]string](location T) string {
return strings.Join(location, "/")
}
// CsvImport imports items from a CSV file. using the standard defined format.
//
// CsvImport applies the following rules/operations
//
// 1. If the item does not exist, it is created.
// 2. If the item has a ImportRef and it exists it is skipped
// 3. Locations and Labels are created if they do not exist.
func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Reader) (int, error) {
sheet := reporting.IOSheet{}
err := sheet.Read(data)
if err != nil {
return 0, err
}
for _, label := range existingLabels {
labels[label.Name] = label.ID
}
for _, row := range loaded {
// ========================================
// Labels
// Locations
if _, exists := locations[row.Location]; !exists {
result, err := svc.repo.Locations.Create(ctx, GID, repo.LocationCreate{
Name: row.Location,
Description: "",
})
if err != nil {
return 0, err
}
locations[row.Location] = result.ID
labelMap := make(map[string]uuid.UUID)
{
labels, err := svc.repo.Labels.GetAll(ctx, GID)
if err != nil {
return 0, err
}
// Labels
for _, label := range row.getLabels() {
if _, exists := labels[label]; exists {
continue
}
result, err := svc.repo.Labels.Create(ctx, GID, repo.LabelCreate{
Name: label,
Description: "",
})
if err != nil {
return 0, err
}
labels[label] = result.ID
for _, label := range labels {
labelMap[label.Name] = label.ID
}
}
highest := repo.AssetID(-1)
// ========================================
// Locations
locationMap := make(map[string]uuid.UUID)
{
locations, err := svc.repo.Locations.Tree(ctx, GID, repo.TreeQuery{WithItems: false})
if err != nil {
return 0, err
}
// Traverse the tree and build a map of location full paths to IDs
// where the full path is the location name joined by slashes.
var traverse func(location *repo.TreeItem, path []string)
traverse = func(location *repo.TreeItem, path []string) {
path = append(path, location.Name)
locationMap[serializeLocation(path)] = location.ID
for _, child := range location.Children {
traverse(child, path)
}
}
for _, location := range locations {
traverse(&location, []string{})
}
}
// ========================================
// Import items
// Asset ID Pre-Check
highestAID := repo.AssetID(-1)
if svc.autoIncrementAssetID {
highest, err = svc.repo.Items.GetHighestAssetID(ctx, GID)
highestAID, err = svc.repo.Items.GetHighestAssetID(ctx, GID)
if err != nil {
return 0, err
}
}
// Create the items
var count int
for _, row := range loaded {
// Check Import Ref
if row.Item.ImportRef != "" {
exists, err := svc.repo.Items.CheckRef(ctx, GID, row.Item.ImportRef)
if exists {
continue
}
finished := 0
for i := range sheet.Rows {
row := sheet.Rows[i]
createRequired := true
// ========================================
// Preflight check for existing item
if row.ImportRef != "" {
exists, err := svc.repo.Items.CheckRef(ctx, GID, row.ImportRef)
if err != nil {
log.Err(err).Msg("error checking import ref")
return 0, fmt.Errorf("error checking for existing item with ref %q: %w", row.ImportRef, err)
}
if exists {
createRequired = false
}
}
locationID := locations[row.Location]
labelIDs := []uuid.UUID{}
for _, label := range row.getLabels() {
labelIDs = append(labelIDs, labels[label])
// ========================================
// Pre-Create Labels as necessary
labelIds := make([]uuid.UUID, len(row.LabelStr))
for j := range row.LabelStr {
label := row.LabelStr[j]
id, ok := labelMap[label]
if !ok {
newLabel, err := svc.repo.Labels.Create(ctx, GID, repo.LabelCreate{Name: label})
if err != nil {
return 0, err
}
id = newLabel.ID
}
labelIds[j] = id
labelMap[label] = id
}
log.Info().
Str("name", row.Item.Name).
Str("location", row.Location).
Msgf("Creating Item: %s", row.Item.Name)
// ========================================
// Pre-Create Locations as necessary
path := serializeLocation(row.Location)
data := repo.ItemCreate{
ImportRef: row.Item.ImportRef,
Name: row.Item.Name,
Description: row.Item.Description,
LabelIDs: labelIDs,
LocationID: locationID,
locationID, ok := locationMap[path]
if !ok { // Traverse the path of LocationStr and check each path element to see if it exists already, if not create it.
paths := []string{}
for i, pathElement := range row.Location {
paths = append(paths, pathElement)
path := serializeLocation(paths)
locationID, ok = locationMap[path]
if !ok {
parentID := uuid.Nil
// Get the parent ID
if i > 0 {
parentPath := serializeLocation(row.Location[:i])
parentID = locationMap[parentPath]
}
newLocation, err := svc.repo.Locations.Create(ctx, GID, repo.LocationCreate{
ParentID: parentID,
Name: pathElement,
})
if err != nil {
return 0, err
}
locationID = newLocation.ID
}
locationMap[path] = locationID
}
locationID, ok = locationMap[path]
if !ok {
return 0, errors.New("failed to create location")
}
}
if svc.autoIncrementAssetID {
highest++
data.AssetID = highest
var effAID repo.AssetID
if svc.autoIncrementAssetID && row.AssetID.Nil() {
effAID = highestAID + 1
highestAID++
} else {
effAID = row.AssetID
}
result, err := svc.repo.Items.Create(ctx, GID, data)
// ========================================
// Create Item
var item repo.ItemOut
switch {
case createRequired:
newItem := repo.ItemCreate{
ImportRef: row.ImportRef,
Name: row.Name,
Description: row.Description,
AssetID: effAID,
LocationID: locationID,
LabelIDs: labelIds,
}
if err != nil {
return count, err
item, err = svc.repo.Items.Create(ctx, GID, newItem)
if err != nil {
return 0, err
}
default:
item, err = svc.repo.Items.GetByRef(ctx, GID, row.ImportRef)
if err != nil {
return 0, err
}
}
// Update the item with the rest of the data
_, err = svc.repo.Items.UpdateByGroup(ctx, GID, repo.ItemUpdate{
// Edges
if item.ID == uuid.Nil {
panic("item ID is nil on import - this should never happen")
}
fields := make([]repo.ItemField, len(row.Fields))
for i := range row.Fields {
fields[i] = repo.ItemField{
Name: row.Fields[i].Name,
Type: "text",
TextValue: row.Fields[i].Value,
}
}
updateItem := repo.ItemUpdate{
ID: item.ID,
LabelIDs: labelIds,
LocationID: locationID,
LabelIDs: labelIDs,
AssetID: data.AssetID,
// General Fields
ID: result.ID,
Name: result.Name,
Description: result.Description,
Insured: row.Item.Insured,
Notes: row.Item.Notes,
Quantity: row.Item.Quantity,
Name: row.Name,
Description: row.Description,
AssetID: effAID,
Insured: row.Insured,
Quantity: row.Quantity,
Archived: row.Archived,
// Identifies the item as imported
SerialNumber: row.Item.SerialNumber,
ModelNumber: row.Item.ModelNumber,
Manufacturer: row.Item.Manufacturer,
PurchasePrice: row.PurchasePrice,
PurchaseFrom: row.PurchaseFrom,
PurchaseTime: row.PurchaseTime,
// Purchase
PurchaseFrom: row.Item.PurchaseFrom,
PurchasePrice: row.Item.PurchasePrice,
PurchaseTime: row.Item.PurchaseTime,
Manufacturer: row.Manufacturer,
ModelNumber: row.ModelNumber,
SerialNumber: row.SerialNumber,
// Warranty
LifetimeWarranty: row.Item.LifetimeWarranty,
WarrantyExpires: row.Item.WarrantyExpires,
WarrantyDetails: row.Item.WarrantyDetails,
LifetimeWarranty: row.LifetimeWarranty,
WarrantyExpires: row.WarrantyExpires,
WarrantyDetails: row.WarrantyDetails,
SoldTo: row.Item.SoldTo,
SoldPrice: row.Item.SoldPrice,
SoldTime: row.Item.SoldTime,
SoldNotes: row.Item.SoldNotes,
})
SoldTo: row.SoldTo,
SoldTime: row.SoldTime,
SoldPrice: row.SoldPrice,
SoldNotes: row.SoldNotes,
if err != nil {
return count, err
Notes: row.Notes,
Fields: fields,
}
count++
item, err = svc.repo.Items.UpdateByGroup(ctx, GID, updateItem)
if err != nil {
return 0, err
}
finished++
}
return count, nil
return finished, nil
}
func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]string, error) {
items, err := svc.repo.Items.GetAll(ctx, GID)
if err != nil {
return nil, err
}
sheet := reporting.IOSheet{}
sheet.ReadItems(items)
return sheet.TSV()
}
func (svc *ItemService) ExportBillOfMaterialsTSV(ctx context.Context, GID uuid.UUID) ([]byte, error) {
items, err := svc.repo.Items.GetAll(ctx, GID)
if err != nil {
return nil, err
}
return reporting.BillOfMaterialsTSV(items)
}

View File

@@ -58,5 +58,4 @@ func TestItemService_AddAttachment(t *testing.T) {
bts, err := os.ReadFile(storedPath)
assert.NoError(t, err)
assert.Equal(t, contents, string(bts))
}

View File

@@ -1,159 +0,0 @@
package services
import (
"bytes"
"encoding/csv"
"errors"
"io"
"strconv"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/data/repo"
)
func determineSeparator(data []byte) (rune, error) {
// First row
firstRow := bytes.Split(data, []byte("\n"))[0]
// find first comma or /t
comma := bytes.IndexByte(firstRow, ',')
tab := bytes.IndexByte(firstRow, '\t')
switch {
case comma == -1 && tab == -1:
return 0, errors.New("could not determine separator")
case tab > comma:
return '\t', nil
default:
return ',', nil
}
}
func ReadCsv(r io.Reader) ([][]string, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
reader := csv.NewReader(bytes.NewReader(data))
// Determine separator
sep, err := determineSeparator(data)
if err != nil {
return nil, err
}
reader.Comma = sep
return reader.ReadAll()
}
var ErrInvalidCsv = errors.New("invalid csv")
const NumOfCols = 21
func parseFloat(s string) float64 {
if s == "" {
return 0
}
f, _ := strconv.ParseFloat(s, 64)
return f
}
func parseDate(s string) time.Time {
if s == "" {
return time.Time{}
}
p, _ := time.Parse("01/02/2006", s)
return p
}
func parseBool(s string) bool {
switch strings.ToLower(s) {
case "true", "yes", "1":
return true
default:
return false
}
}
func parseInt(s string) int {
i, _ := strconv.Atoi(s)
return i
}
type csvRow struct {
Item repo.ItemOut
Location string
LabelStr string
}
func newCsvRow(row []string) csvRow {
return csvRow{
Location: row[1],
LabelStr: row[2],
Item: repo.ItemOut{
ItemSummary: repo.ItemSummary{
ImportRef: row[0],
Quantity: parseInt(row[3]),
Name: row[4],
Description: row[5],
Insured: parseBool(row[6]),
PurchasePrice: parseFloat(row[12]),
},
SerialNumber: row[7],
ModelNumber: row[8],
Manufacturer: row[9],
Notes: row[10],
PurchaseFrom: row[11],
PurchaseTime: parseDate(row[13]),
LifetimeWarranty: parseBool(row[14]),
WarrantyExpires: parseDate(row[15]),
WarrantyDetails: row[16],
SoldTo: row[17],
SoldPrice: parseFloat(row[18]),
SoldTime: parseDate(row[19]),
SoldNotes: row[20],
},
}
}
func (c csvRow) getLabels() []string {
split := strings.Split(c.LabelStr, ";")
// Trim each
for i, s := range split {
split[i] = strings.TrimSpace(s)
}
// Remove empty
for i, s := range split {
if s == "" {
split = append(split[:i], split[i+1:]...)
}
}
return split
}
func (c csvRow) validate() []error {
var errs []error
add := func(err error) {
errs = append(errs, err)
}
required := func(s string, name string) {
if s == "" {
add(errors.New(name + " is required"))
}
}
required(c.Location, "Location")
required(c.Item.Name, "Name")
return errs
}

View File

@@ -1,164 +0,0 @@
package services
import (
"bytes"
_ "embed"
"encoding/csv"
"fmt"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
//go:embed testdata/import.csv
var CSVData_Comma []byte
//go:embed testdata/import.tsv
var CSVData_Tab []byte
func loadcsv() [][]string {
reader := csv.NewReader(bytes.NewReader(CSVData_Comma))
records, err := reader.ReadAll()
if err != nil {
panic(err)
}
return records
}
func Test_CorrectDateParsing(t *testing.T) {
t.Parallel()
expected := []time.Time{
time.Date(2021, 10, 13, 0, 0, 0, 0, time.UTC),
time.Date(2021, 10, 15, 0, 0, 0, 0, time.UTC),
time.Date(2021, 10, 13, 0, 0, 0, 0, time.UTC),
time.Date(2020, 10, 21, 0, 0, 0, 0, time.UTC),
time.Date(2020, 10, 14, 0, 0, 0, 0, time.UTC),
time.Date(2020, 9, 30, 0, 0, 0, 0, time.UTC),
}
records := loadcsv()
for i, record := range records {
if i == 0 {
continue
}
entity := newCsvRow(record)
expected := expected[i-1]
assert.Equal(t, expected, entity.Item.PurchaseTime, fmt.Sprintf("Failed on row %d", i))
assert.Equal(t, expected, entity.Item.WarrantyExpires, fmt.Sprintf("Failed on row %d", i))
assert.Equal(t, expected, entity.Item.SoldTime, fmt.Sprintf("Failed on row %d", i))
}
}
func Test_csvRow_getLabels(t *testing.T) {
type fields struct {
LabelStr string
}
tests := []struct {
name string
fields fields
want []string
}{
{
name: "basic test",
fields: fields{
LabelStr: "IOT;Home Assistant;Z-Wave",
},
want: []string{"IOT", "Home Assistant", "Z-Wave"},
},
{
name: "no labels",
fields: fields{
LabelStr: "",
},
want: []string{},
},
{
name: "single label",
fields: fields{
LabelStr: "IOT",
},
want: []string{"IOT"},
},
{
name: "trailing semicolon",
fields: fields{
LabelStr: "IOT;",
},
want: []string{"IOT"},
},
{
name: "whitespace",
fields: fields{
LabelStr: " IOT; Home Assistant; Z-Wave ",
},
want: []string{"IOT", "Home Assistant", "Z-Wave"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := csvRow{
LabelStr: tt.fields.LabelStr,
}
if got := c.getLabels(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("csvRow.getLabels() = %v, want %v", got, tt.want)
}
})
}
}
func Test_determineSeparator(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
want rune
wantErr bool
}{
{
name: "comma",
args: args{
data: CSVData_Comma,
},
want: ',',
wantErr: false,
},
{
name: "tab",
args: args{
data: CSVData_Tab,
},
want: '\t',
wantErr: false,
},
{
name: "invalid",
args: args{
data: []byte("a;b;c"),
},
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := determineSeparator(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("determineSeparator() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("determineSeparator() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,78 +0,0 @@
package services
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert"
)
func TestItemService_CsvImport(t *testing.T) {
data := loadcsv()
svc := &ItemService{
repo: tRepos,
}
count, err := svc.CsvImport(context.Background(), tGroup.ID, data)
assert.Equal(t, 6, count)
assert.NoError(t, err)
// Check import refs are deduplicated
count, err = svc.CsvImport(context.Background(), tGroup.ID, data)
assert.Equal(t, 0, count)
assert.NoError(t, err)
items, err := svc.repo.Items.GetAll(context.Background(), tGroup.ID)
assert.NoError(t, err)
t.Cleanup(func() {
for _, item := range items {
err := svc.repo.Items.Delete(context.Background(), item.ID)
assert.NoError(t, err)
}
})
assert.Equal(t, len(items), 6)
dataCsv := []csvRow{}
for _, item := range data {
dataCsv = append(dataCsv, newCsvRow(item))
}
allLocation, err := tRepos.Locations.GetAll(context.Background(), tGroup.ID, repo.LocationQuery{})
assert.NoError(t, err)
locNames := []string{}
for _, loc := range allLocation {
locNames = append(locNames, loc.Name)
}
allLabels, err := tRepos.Labels.GetAll(context.Background(), tGroup.ID)
assert.NoError(t, err)
labelNames := []string{}
for _, label := range allLabels {
labelNames = append(labelNames, label.Name)
}
ids := []uuid.UUID{}
t.Cleanup((func() {
for _, id := range ids {
err := svc.repo.Items.Delete(context.Background(), id)
assert.NoError(t, err)
}
}))
for _, item := range items {
assert.Contains(t, locNames, item.Location.Name)
for _, label := range item.Labels {
assert.Contains(t, labelNames, label.Name)
}
for _, csvRow := range dataCsv {
if csvRow.Item.Name == item.Name {
assert.Equal(t, csvRow.Item.Description, item.Description)
assert.Equal(t, csvRow.Item.Quantity, item.Quantity)
assert.Equal(t, csvRow.Item.Insured, item.Insured)
}
}
}
}

View File

@@ -51,21 +51,25 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
Msg("Registering new user")
var (
err error
group repo.Group
token repo.GroupInvitation
isOwner = false
err error
group repo.Group
token repo.GroupInvitation
// creatingGroup is true if the user is creating a new group.
creatingGroup = false
)
switch data.GroupToken {
case "":
isOwner = true
log.Debug().Msg("creating new group")
creatingGroup = true
group, err = svc.repos.Groups.GroupCreate(ctx, "Home")
if err != nil {
log.Err(err).Msg("Failed to create group")
return repo.UserOut{}, err
}
default:
log.Debug().Msg("joining existing group")
token, err = svc.repos.Groups.InvitationGet(ctx, hasher.HashToken(data.GroupToken))
if err != nil {
log.Err(err).Msg("Failed to get invitation token")
@@ -81,7 +85,7 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
Password: hashed,
IsSuperuser: false,
GroupID: group.ID,
IsOwner: isOwner,
IsOwner: creatingGroup,
}
usr, err := svc.repos.Users.Create(ctx, usrCreate)
@@ -89,21 +93,24 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
return repo.UserOut{}, err
}
for _, label := range defaultLabels() {
_, err := svc.repos.Labels.Create(ctx, group.ID, label)
if err != nil {
return repo.UserOut{}, err
// Create the default labels and locations for the group.
if creatingGroup {
for _, label := range defaultLabels() {
_, err := svc.repos.Labels.Create(ctx, usr.GroupID, label)
if err != nil {
return repo.UserOut{}, err
}
}
for _, location := range defaultLocations() {
_, err := svc.repos.Locations.Create(ctx, usr.GroupID, location)
if err != nil {
return repo.UserOut{}, err
}
}
}
for _, location := range defaultLocations() {
_, err := svc.repos.Locations.Create(ctx, group.ID, location)
if err != nil {
return repo.UserOut{}, err
}
}
// Decrement the invitation token if it was used
// Decrement the invitation token if it was used.
if token.ID != uuid.Nil {
err = svc.repos.Groups.InvitationUpdate(ctx, token.ID, token.Uses-1)
if err != nil {
@@ -133,13 +140,18 @@ func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo.
// ============================================================================
// User Authentication
func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID) (UserAuthTokenDetail, error) {
func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID, extendedSession bool) (UserAuthTokenDetail, error) {
attachmentToken := hasher.GenerateToken()
expiresAt := time.Now().Add(oneWeek)
if extendedSession {
expiresAt = time.Now().Add(oneWeek * 4)
}
attachmentData := repo.UserAuthTokenCreate{
UserID: userId,
TokenHash: attachmentToken.Hash,
ExpiresAt: time.Now().Add(oneWeek),
ExpiresAt: expiresAt,
}
_, err := svc.repos.AuthTokens.CreateToken(ctx, attachmentData, authroles.RoleAttachments)
@@ -151,7 +163,7 @@ func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID
data := repo.UserAuthTokenCreate{
UserID: userId,
TokenHash: userToken.Hash,
ExpiresAt: time.Now().Add(oneWeek),
ExpiresAt: expiresAt,
}
created, err := svc.repos.AuthTokens.CreateToken(ctx, data, authroles.RoleUser)
@@ -166,9 +178,8 @@ func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID
}, nil
}
func (svc *UserService) Login(ctx context.Context, username, password string) (UserAuthTokenDetail, error) {
func (svc *UserService) Login(ctx context.Context, username, password string, extendedSession bool) (UserAuthTokenDetail, error) {
usr, err := svc.repos.Users.GetOneEmail(ctx, username)
if err != nil {
// SECURITY: Perform hash to ensure response times are the same
hasher.CheckPasswordHash("not-a-real-password", "not-a-real-password")
@@ -179,7 +190,7 @@ func (svc *UserService) Login(ctx context.Context, username, password string) (U
return UserAuthTokenDetail{}, ErrorInvalidLogin
}
return svc.createSessionToken(ctx, usr.ID)
return svc.createSessionToken(ctx, usr.ID, extendedSession)
}
func (svc *UserService) Logout(ctx context.Context, token string) error {
@@ -192,12 +203,11 @@ func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthT
hash := hasher.HashToken(token)
dbToken, err := svc.repos.AuthTokens.GetUserFromToken(ctx, hash)
if err != nil {
return UserAuthTokenDetail{}, ErrorInvalidToken
}
return svc.createSessionToken(ctx, dbToken.ID)
return svc.createSessionToken(ctx, dbToken.ID, false)
}
// DeleteSelf deletes the user that is currently logged based of the provided UUID

View File

@@ -144,19 +144,19 @@ func (a *Attachment) assignValues(columns []string, values []any) error {
// QueryItem queries the "item" edge of the Attachment entity.
func (a *Attachment) QueryItem() *ItemQuery {
return (&AttachmentClient{config: a.config}).QueryItem(a)
return NewAttachmentClient(a.config).QueryItem(a)
}
// QueryDocument queries the "document" edge of the Attachment entity.
func (a *Attachment) QueryDocument() *DocumentQuery {
return (&AttachmentClient{config: a.config}).QueryDocument(a)
return NewAttachmentClient(a.config).QueryDocument(a)
}
// Update returns a builder for updating this Attachment.
// Note that you need to call Attachment.Unwrap() before calling this method if this Attachment
// was returned from a transaction, and the transaction was committed or rolled back.
func (a *Attachment) Update() *AttachmentUpdateOne {
return (&AttachmentClient{config: a.config}).UpdateOne(a)
return NewAttachmentClient(a.config).UpdateOne(a)
}
// Unwrap unwraps the Attachment entity that was returned from a transaction after it was closed,
@@ -189,9 +189,3 @@ func (a *Attachment) String() string {
// Attachments is a parsable slice of Attachment.
type Attachments []*Attachment
func (a Attachments) config(cfg config) {
for _i := range a {
a[_i].config = cfg
}
}

View File

@@ -205,13 +205,7 @@ func (ac *AttachmentCreate) sqlSave(ctx context.Context) (*Attachment, error) {
func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
var (
_node = &Attachment{config: ac.config}
_spec = &sqlgraph.CreateSpec{
Table: attachment.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}
_spec = sqlgraph.NewCreateSpec(attachment.Table, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
)
if id, ok := ac.mutation.ID(); ok {
_node.ID = id
@@ -237,10 +231,7 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -257,10 +248,7 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -40,15 +40,7 @@ func (ad *AttachmentDelete) ExecX(ctx context.Context) int {
}
func (ad *AttachmentDelete) sqlExec(ctx context.Context) (int, error) {
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
_spec := sqlgraph.NewDeleteSpec(attachment.Table, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
if ps := ad.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -69,6 +61,12 @@ type AttachmentDeleteOne struct {
ad *AttachmentDelete
}
// Where appends a list predicates to the AttachmentDelete builder.
func (ado *AttachmentDeleteOne) Where(ps ...predicate.Attachment) *AttachmentDeleteOne {
ado.ad.mutation.Where(ps...)
return ado
}
// Exec executes the deletion query.
func (ado *AttachmentDeleteOne) Exec(ctx context.Context) error {
n, err := ado.ad.Exec(ctx)
@@ -84,5 +82,7 @@ func (ado *AttachmentDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (ado *AttachmentDeleteOne) ExecX(ctx context.Context) {
ado.ad.ExecX(ctx)
if err := ado.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -20,11 +20,8 @@ import (
// AttachmentQuery is the builder for querying Attachment entities.
type AttachmentQuery struct {
config
limit *int
offset *int
unique *bool
ctx *QueryContext
order []OrderFunc
fields []string
inters []Interceptor
predicates []predicate.Attachment
withItem *ItemQuery
@@ -43,20 +40,20 @@ func (aq *AttachmentQuery) Where(ps ...predicate.Attachment) *AttachmentQuery {
// Limit the number of records to be returned by this query.
func (aq *AttachmentQuery) Limit(limit int) *AttachmentQuery {
aq.limit = &limit
aq.ctx.Limit = &limit
return aq
}
// Offset to start from.
func (aq *AttachmentQuery) Offset(offset int) *AttachmentQuery {
aq.offset = &offset
aq.ctx.Offset = &offset
return aq
}
// 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 (aq *AttachmentQuery) Unique(unique bool) *AttachmentQuery {
aq.unique = &unique
aq.ctx.Unique = &unique
return aq
}
@@ -113,7 +110,7 @@ func (aq *AttachmentQuery) QueryDocument() *DocumentQuery {
// First returns the first Attachment entity from the query.
// Returns a *NotFoundError when no Attachment was found.
func (aq *AttachmentQuery) First(ctx context.Context) (*Attachment, error) {
nodes, err := aq.Limit(1).All(newQueryContext(ctx, TypeAttachment, "First"))
nodes, err := aq.Limit(1).All(setContextOp(ctx, aq.ctx, "First"))
if err != nil {
return nil, err
}
@@ -136,7 +133,7 @@ func (aq *AttachmentQuery) FirstX(ctx context.Context) *Attachment {
// Returns a *NotFoundError when no Attachment ID was found.
func (aq *AttachmentQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = aq.Limit(1).IDs(newQueryContext(ctx, TypeAttachment, "FirstID")); err != nil {
if ids, err = aq.Limit(1).IDs(setContextOp(ctx, aq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
@@ -159,7 +156,7 @@ func (aq *AttachmentQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one Attachment entity is found.
// Returns a *NotFoundError when no Attachment entities are found.
func (aq *AttachmentQuery) Only(ctx context.Context) (*Attachment, error) {
nodes, err := aq.Limit(2).All(newQueryContext(ctx, TypeAttachment, "Only"))
nodes, err := aq.Limit(2).All(setContextOp(ctx, aq.ctx, "Only"))
if err != nil {
return nil, err
}
@@ -187,7 +184,7 @@ func (aq *AttachmentQuery) OnlyX(ctx context.Context) *Attachment {
// Returns a *NotFoundError when no entities are found.
func (aq *AttachmentQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = aq.Limit(2).IDs(newQueryContext(ctx, TypeAttachment, "OnlyID")); err != nil {
if ids, err = aq.Limit(2).IDs(setContextOp(ctx, aq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
@@ -212,7 +209,7 @@ func (aq *AttachmentQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of Attachments.
func (aq *AttachmentQuery) All(ctx context.Context) ([]*Attachment, error) {
ctx = newQueryContext(ctx, TypeAttachment, "All")
ctx = setContextOp(ctx, aq.ctx, "All")
if err := aq.prepareQuery(ctx); err != nil {
return nil, err
}
@@ -230,10 +227,12 @@ func (aq *AttachmentQuery) AllX(ctx context.Context) []*Attachment {
}
// IDs executes the query and returns a list of Attachment IDs.
func (aq *AttachmentQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
ctx = newQueryContext(ctx, TypeAttachment, "IDs")
if err := aq.Select(attachment.FieldID).Scan(ctx, &ids); err != nil {
func (aq *AttachmentQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if aq.ctx.Unique == nil && aq.path != nil {
aq.Unique(true)
}
ctx = setContextOp(ctx, aq.ctx, "IDs")
if err = aq.Select(attachment.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@@ -250,7 +249,7 @@ func (aq *AttachmentQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (aq *AttachmentQuery) Count(ctx context.Context) (int, error) {
ctx = newQueryContext(ctx, TypeAttachment, "Count")
ctx = setContextOp(ctx, aq.ctx, "Count")
if err := aq.prepareQuery(ctx); err != nil {
return 0, err
}
@@ -268,7 +267,7 @@ func (aq *AttachmentQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (aq *AttachmentQuery) Exist(ctx context.Context) (bool, error) {
ctx = newQueryContext(ctx, TypeAttachment, "Exist")
ctx = setContextOp(ctx, aq.ctx, "Exist")
switch _, err := aq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
@@ -296,17 +295,15 @@ func (aq *AttachmentQuery) Clone() *AttachmentQuery {
}
return &AttachmentQuery{
config: aq.config,
limit: aq.limit,
offset: aq.offset,
ctx: aq.ctx.Clone(),
order: append([]OrderFunc{}, aq.order...),
inters: append([]Interceptor{}, aq.inters...),
predicates: append([]predicate.Attachment{}, aq.predicates...),
withItem: aq.withItem.Clone(),
withDocument: aq.withDocument.Clone(),
// clone intermediate query.
sql: aq.sql.Clone(),
path: aq.path,
unique: aq.unique,
sql: aq.sql.Clone(),
path: aq.path,
}
}
@@ -347,9 +344,9 @@ func (aq *AttachmentQuery) WithDocument(opts ...func(*DocumentQuery)) *Attachmen
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (aq *AttachmentQuery) GroupBy(field string, fields ...string) *AttachmentGroupBy {
aq.fields = append([]string{field}, fields...)
aq.ctx.Fields = append([]string{field}, fields...)
grbuild := &AttachmentGroupBy{build: aq}
grbuild.flds = &aq.fields
grbuild.flds = &aq.ctx.Fields
grbuild.label = attachment.Label
grbuild.scan = grbuild.Scan
return grbuild
@@ -368,10 +365,10 @@ func (aq *AttachmentQuery) GroupBy(field string, fields ...string) *AttachmentGr
// Select(attachment.FieldCreatedAt).
// Scan(ctx, &v)
func (aq *AttachmentQuery) Select(fields ...string) *AttachmentSelect {
aq.fields = append(aq.fields, fields...)
aq.ctx.Fields = append(aq.ctx.Fields, fields...)
sbuild := &AttachmentSelect{AttachmentQuery: aq}
sbuild.label = attachment.Label
sbuild.flds, sbuild.scan = &aq.fields, sbuild.Scan
sbuild.flds, sbuild.scan = &aq.ctx.Fields, sbuild.Scan
return sbuild
}
@@ -391,7 +388,7 @@ func (aq *AttachmentQuery) prepareQuery(ctx context.Context) error {
}
}
}
for _, f := range aq.fields {
for _, f := range aq.ctx.Fields {
if !attachment.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@@ -468,6 +465,9 @@ func (aq *AttachmentQuery) loadItem(ctx context.Context, query *ItemQuery, nodes
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(item.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@@ -497,6 +497,9 @@ func (aq *AttachmentQuery) loadDocument(ctx context.Context, query *DocumentQuer
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(document.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@@ -516,30 +519,22 @@ func (aq *AttachmentQuery) loadDocument(ctx context.Context, query *DocumentQuer
func (aq *AttachmentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := aq.querySpec()
_spec.Node.Columns = aq.fields
if len(aq.fields) > 0 {
_spec.Unique = aq.unique != nil && *aq.unique
_spec.Node.Columns = aq.ctx.Fields
if len(aq.ctx.Fields) > 0 {
_spec.Unique = aq.ctx.Unique != nil && *aq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, aq.driver, _spec)
}
func (aq *AttachmentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
Columns: attachment.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
From: aq.sql,
Unique: true,
}
if unique := aq.unique; unique != nil {
_spec := sqlgraph.NewQuerySpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
_spec.From = aq.sql
if unique := aq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if aq.path != nil {
_spec.Unique = true
}
if fields := aq.fields; len(fields) > 0 {
if fields := aq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, attachment.FieldID)
for i := range fields {
@@ -555,10 +550,10 @@ func (aq *AttachmentQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := aq.limit; limit != nil {
if limit := aq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := aq.offset; offset != nil {
if offset := aq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := aq.order; len(ps) > 0 {
@@ -574,7 +569,7 @@ func (aq *AttachmentQuery) querySpec() *sqlgraph.QuerySpec {
func (aq *AttachmentQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(aq.driver.Dialect())
t1 := builder.Table(attachment.Table)
columns := aq.fields
columns := aq.ctx.Fields
if len(columns) == 0 {
columns = attachment.Columns
}
@@ -583,7 +578,7 @@ func (aq *AttachmentQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = aq.sql
selector.Select(selector.Columns(columns...)...)
}
if aq.unique != nil && *aq.unique {
if aq.ctx.Unique != nil && *aq.ctx.Unique {
selector.Distinct()
}
for _, p := range aq.predicates {
@@ -592,12 +587,12 @@ func (aq *AttachmentQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range aq.order {
p(selector)
}
if offset := aq.offset; offset != nil {
if offset := aq.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 := aq.limit; limit != nil {
if limit := aq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
@@ -617,7 +612,7 @@ func (agb *AttachmentGroupBy) Aggregate(fns ...AggregateFunc) *AttachmentGroupBy
// Scan applies the selector query and scans the result into the given value.
func (agb *AttachmentGroupBy) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeAttachment, "GroupBy")
ctx = setContextOp(ctx, agb.build.ctx, "GroupBy")
if err := agb.build.prepareQuery(ctx); err != nil {
return err
}
@@ -665,7 +660,7 @@ func (as *AttachmentSelect) Aggregate(fns ...AggregateFunc) *AttachmentSelect {
// Scan applies the selector query and scans the result into the given value.
func (as *AttachmentSelect) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeAttachment, "Select")
ctx = setContextOp(ctx, as.ctx, "Select")
if err := as.prepareQuery(ctx); err != nil {
return err
}

View File

@@ -146,16 +146,7 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := au.check(); err != nil {
return n, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
Columns: attachment.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
if ps := au.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -177,10 +168,7 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -193,10 +181,7 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -212,10 +197,7 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -228,10 +210,7 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -318,6 +297,12 @@ func (auo *AttachmentUpdateOne) ClearDocument() *AttachmentUpdateOne {
return auo
}
// Where appends a list predicates to the AttachmentUpdate builder.
func (auo *AttachmentUpdateOne) Where(ps ...predicate.Attachment) *AttachmentUpdateOne {
auo.mutation.Where(ps...)
return auo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (auo *AttachmentUpdateOne) Select(field string, fields ...string) *AttachmentUpdateOne {
@@ -381,16 +366,7 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
if err := auo.check(); err != nil {
return _node, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
Columns: attachment.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
id, ok := auo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Attachment.id" for update`)}
@@ -429,10 +405,7 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -445,10 +418,7 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -464,10 +434,7 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -480,10 +447,7 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -99,14 +99,14 @@ func (ar *AuthRoles) assignValues(columns []string, values []any) error {
// QueryToken queries the "token" edge of the AuthRoles entity.
func (ar *AuthRoles) QueryToken() *AuthTokensQuery {
return (&AuthRolesClient{config: ar.config}).QueryToken(ar)
return NewAuthRolesClient(ar.config).QueryToken(ar)
}
// Update returns a builder for updating this AuthRoles.
// Note that you need to call AuthRoles.Unwrap() before calling this method if this AuthRoles
// was returned from a transaction, and the transaction was committed or rolled back.
func (ar *AuthRoles) Update() *AuthRolesUpdateOne {
return (&AuthRolesClient{config: ar.config}).UpdateOne(ar)
return NewAuthRolesClient(ar.config).UpdateOne(ar)
}
// Unwrap unwraps the AuthRoles entity that was returned from a transaction after it was closed,
@@ -133,9 +133,3 @@ func (ar *AuthRoles) String() string {
// AuthRolesSlice is a parsable slice of AuthRoles.
type AuthRolesSlice []*AuthRoles
func (ar AuthRolesSlice) config(cfg config) {
for _i := range ar {
ar[_i].config = cfg
}
}

View File

@@ -129,13 +129,7 @@ func (arc *AuthRolesCreate) sqlSave(ctx context.Context) (*AuthRoles, error) {
func (arc *AuthRolesCreate) createSpec() (*AuthRoles, *sqlgraph.CreateSpec) {
var (
_node = &AuthRoles{config: arc.config}
_spec = &sqlgraph.CreateSpec{
Table: authroles.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
}
_spec = sqlgraph.NewCreateSpec(authroles.Table, sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt))
)
if value, ok := arc.mutation.Role(); ok {
_spec.SetField(authroles.FieldRole, field.TypeEnum, value)
@@ -149,10 +143,7 @@ func (arc *AuthRolesCreate) createSpec() (*AuthRoles, *sqlgraph.CreateSpec) {
Columns: []string{authroles.TokenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -40,15 +40,7 @@ func (ard *AuthRolesDelete) ExecX(ctx context.Context) int {
}
func (ard *AuthRolesDelete) sqlExec(ctx context.Context) (int, error) {
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: authroles.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
},
}
_spec := sqlgraph.NewDeleteSpec(authroles.Table, sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt))
if ps := ard.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -69,6 +61,12 @@ type AuthRolesDeleteOne struct {
ard *AuthRolesDelete
}
// Where appends a list predicates to the AuthRolesDelete builder.
func (ardo *AuthRolesDeleteOne) Where(ps ...predicate.AuthRoles) *AuthRolesDeleteOne {
ardo.ard.mutation.Where(ps...)
return ardo
}
// Exec executes the deletion query.
func (ardo *AuthRolesDeleteOne) Exec(ctx context.Context) error {
n, err := ardo.ard.Exec(ctx)
@@ -84,5 +82,7 @@ func (ardo *AuthRolesDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (ardo *AuthRolesDeleteOne) ExecX(ctx context.Context) {
ardo.ard.ExecX(ctx)
if err := ardo.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -19,11 +19,8 @@ import (
// AuthRolesQuery is the builder for querying AuthRoles entities.
type AuthRolesQuery struct {
config
limit *int
offset *int
unique *bool
ctx *QueryContext
order []OrderFunc
fields []string
inters []Interceptor
predicates []predicate.AuthRoles
withToken *AuthTokensQuery
@@ -41,20 +38,20 @@ func (arq *AuthRolesQuery) Where(ps ...predicate.AuthRoles) *AuthRolesQuery {
// Limit the number of records to be returned by this query.
func (arq *AuthRolesQuery) Limit(limit int) *AuthRolesQuery {
arq.limit = &limit
arq.ctx.Limit = &limit
return arq
}
// Offset to start from.
func (arq *AuthRolesQuery) Offset(offset int) *AuthRolesQuery {
arq.offset = &offset
arq.ctx.Offset = &offset
return arq
}
// 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 (arq *AuthRolesQuery) Unique(unique bool) *AuthRolesQuery {
arq.unique = &unique
arq.ctx.Unique = &unique
return arq
}
@@ -89,7 +86,7 @@ func (arq *AuthRolesQuery) QueryToken() *AuthTokensQuery {
// First returns the first AuthRoles entity from the query.
// Returns a *NotFoundError when no AuthRoles was found.
func (arq *AuthRolesQuery) First(ctx context.Context) (*AuthRoles, error) {
nodes, err := arq.Limit(1).All(newQueryContext(ctx, TypeAuthRoles, "First"))
nodes, err := arq.Limit(1).All(setContextOp(ctx, arq.ctx, "First"))
if err != nil {
return nil, err
}
@@ -112,7 +109,7 @@ func (arq *AuthRolesQuery) FirstX(ctx context.Context) *AuthRoles {
// Returns a *NotFoundError when no AuthRoles ID was found.
func (arq *AuthRolesQuery) FirstID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = arq.Limit(1).IDs(newQueryContext(ctx, TypeAuthRoles, "FirstID")); err != nil {
if ids, err = arq.Limit(1).IDs(setContextOp(ctx, arq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
@@ -135,7 +132,7 @@ func (arq *AuthRolesQuery) FirstIDX(ctx context.Context) int {
// Returns a *NotSingularError when more than one AuthRoles entity is found.
// Returns a *NotFoundError when no AuthRoles entities are found.
func (arq *AuthRolesQuery) Only(ctx context.Context) (*AuthRoles, error) {
nodes, err := arq.Limit(2).All(newQueryContext(ctx, TypeAuthRoles, "Only"))
nodes, err := arq.Limit(2).All(setContextOp(ctx, arq.ctx, "Only"))
if err != nil {
return nil, err
}
@@ -163,7 +160,7 @@ func (arq *AuthRolesQuery) OnlyX(ctx context.Context) *AuthRoles {
// Returns a *NotFoundError when no entities are found.
func (arq *AuthRolesQuery) OnlyID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = arq.Limit(2).IDs(newQueryContext(ctx, TypeAuthRoles, "OnlyID")); err != nil {
if ids, err = arq.Limit(2).IDs(setContextOp(ctx, arq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
@@ -188,7 +185,7 @@ func (arq *AuthRolesQuery) OnlyIDX(ctx context.Context) int {
// All executes the query and returns a list of AuthRolesSlice.
func (arq *AuthRolesQuery) All(ctx context.Context) ([]*AuthRoles, error) {
ctx = newQueryContext(ctx, TypeAuthRoles, "All")
ctx = setContextOp(ctx, arq.ctx, "All")
if err := arq.prepareQuery(ctx); err != nil {
return nil, err
}
@@ -206,10 +203,12 @@ func (arq *AuthRolesQuery) AllX(ctx context.Context) []*AuthRoles {
}
// IDs executes the query and returns a list of AuthRoles IDs.
func (arq *AuthRolesQuery) IDs(ctx context.Context) ([]int, error) {
var ids []int
ctx = newQueryContext(ctx, TypeAuthRoles, "IDs")
if err := arq.Select(authroles.FieldID).Scan(ctx, &ids); err != nil {
func (arq *AuthRolesQuery) IDs(ctx context.Context) (ids []int, err error) {
if arq.ctx.Unique == nil && arq.path != nil {
arq.Unique(true)
}
ctx = setContextOp(ctx, arq.ctx, "IDs")
if err = arq.Select(authroles.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@@ -226,7 +225,7 @@ func (arq *AuthRolesQuery) IDsX(ctx context.Context) []int {
// Count returns the count of the given query.
func (arq *AuthRolesQuery) Count(ctx context.Context) (int, error) {
ctx = newQueryContext(ctx, TypeAuthRoles, "Count")
ctx = setContextOp(ctx, arq.ctx, "Count")
if err := arq.prepareQuery(ctx); err != nil {
return 0, err
}
@@ -244,7 +243,7 @@ func (arq *AuthRolesQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (arq *AuthRolesQuery) Exist(ctx context.Context) (bool, error) {
ctx = newQueryContext(ctx, TypeAuthRoles, "Exist")
ctx = setContextOp(ctx, arq.ctx, "Exist")
switch _, err := arq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
@@ -272,16 +271,14 @@ func (arq *AuthRolesQuery) Clone() *AuthRolesQuery {
}
return &AuthRolesQuery{
config: arq.config,
limit: arq.limit,
offset: arq.offset,
ctx: arq.ctx.Clone(),
order: append([]OrderFunc{}, arq.order...),
inters: append([]Interceptor{}, arq.inters...),
predicates: append([]predicate.AuthRoles{}, arq.predicates...),
withToken: arq.withToken.Clone(),
// clone intermediate query.
sql: arq.sql.Clone(),
path: arq.path,
unique: arq.unique,
sql: arq.sql.Clone(),
path: arq.path,
}
}
@@ -311,9 +308,9 @@ func (arq *AuthRolesQuery) WithToken(opts ...func(*AuthTokensQuery)) *AuthRolesQ
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (arq *AuthRolesQuery) GroupBy(field string, fields ...string) *AuthRolesGroupBy {
arq.fields = append([]string{field}, fields...)
arq.ctx.Fields = append([]string{field}, fields...)
grbuild := &AuthRolesGroupBy{build: arq}
grbuild.flds = &arq.fields
grbuild.flds = &arq.ctx.Fields
grbuild.label = authroles.Label
grbuild.scan = grbuild.Scan
return grbuild
@@ -332,10 +329,10 @@ func (arq *AuthRolesQuery) GroupBy(field string, fields ...string) *AuthRolesGro
// Select(authroles.FieldRole).
// Scan(ctx, &v)
func (arq *AuthRolesQuery) Select(fields ...string) *AuthRolesSelect {
arq.fields = append(arq.fields, fields...)
arq.ctx.Fields = append(arq.ctx.Fields, fields...)
sbuild := &AuthRolesSelect{AuthRolesQuery: arq}
sbuild.label = authroles.Label
sbuild.flds, sbuild.scan = &arq.fields, sbuild.Scan
sbuild.flds, sbuild.scan = &arq.ctx.Fields, sbuild.Scan
return sbuild
}
@@ -355,7 +352,7 @@ func (arq *AuthRolesQuery) prepareQuery(ctx context.Context) error {
}
}
}
for _, f := range arq.fields {
for _, f := range arq.ctx.Fields {
if !authroles.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@@ -425,6 +422,9 @@ func (arq *AuthRolesQuery) loadToken(ctx context.Context, query *AuthTokensQuery
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(authtokens.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@@ -444,30 +444,22 @@ func (arq *AuthRolesQuery) loadToken(ctx context.Context, query *AuthTokensQuery
func (arq *AuthRolesQuery) sqlCount(ctx context.Context) (int, error) {
_spec := arq.querySpec()
_spec.Node.Columns = arq.fields
if len(arq.fields) > 0 {
_spec.Unique = arq.unique != nil && *arq.unique
_spec.Node.Columns = arq.ctx.Fields
if len(arq.ctx.Fields) > 0 {
_spec.Unique = arq.ctx.Unique != nil && *arq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, arq.driver, _spec)
}
func (arq *AuthRolesQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: authroles.Table,
Columns: authroles.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
},
From: arq.sql,
Unique: true,
}
if unique := arq.unique; unique != nil {
_spec := sqlgraph.NewQuerySpec(authroles.Table, authroles.Columns, sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt))
_spec.From = arq.sql
if unique := arq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if arq.path != nil {
_spec.Unique = true
}
if fields := arq.fields; len(fields) > 0 {
if fields := arq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, authroles.FieldID)
for i := range fields {
@@ -483,10 +475,10 @@ func (arq *AuthRolesQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := arq.limit; limit != nil {
if limit := arq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := arq.offset; offset != nil {
if offset := arq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := arq.order; len(ps) > 0 {
@@ -502,7 +494,7 @@ func (arq *AuthRolesQuery) querySpec() *sqlgraph.QuerySpec {
func (arq *AuthRolesQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(arq.driver.Dialect())
t1 := builder.Table(authroles.Table)
columns := arq.fields
columns := arq.ctx.Fields
if len(columns) == 0 {
columns = authroles.Columns
}
@@ -511,7 +503,7 @@ func (arq *AuthRolesQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = arq.sql
selector.Select(selector.Columns(columns...)...)
}
if arq.unique != nil && *arq.unique {
if arq.ctx.Unique != nil && *arq.ctx.Unique {
selector.Distinct()
}
for _, p := range arq.predicates {
@@ -520,12 +512,12 @@ func (arq *AuthRolesQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range arq.order {
p(selector)
}
if offset := arq.offset; offset != nil {
if offset := arq.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 := arq.limit; limit != nil {
if limit := arq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
@@ -545,7 +537,7 @@ func (argb *AuthRolesGroupBy) Aggregate(fns ...AggregateFunc) *AuthRolesGroupBy
// Scan applies the selector query and scans the result into the given value.
func (argb *AuthRolesGroupBy) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeAuthRoles, "GroupBy")
ctx = setContextOp(ctx, argb.build.ctx, "GroupBy")
if err := argb.build.prepareQuery(ctx); err != nil {
return err
}
@@ -593,7 +585,7 @@ func (ars *AuthRolesSelect) Aggregate(fns ...AggregateFunc) *AuthRolesSelect {
// Scan applies the selector query and scans the result into the given value.
func (ars *AuthRolesSelect) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeAuthRoles, "Select")
ctx = setContextOp(ctx, ars.ctx, "Select")
if err := ars.prepareQuery(ctx); err != nil {
return err
}

View File

@@ -114,16 +114,7 @@ func (aru *AuthRolesUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := aru.check(); err != nil {
return n, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: authroles.Table,
Columns: authroles.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(authroles.Table, authroles.Columns, sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt))
if ps := aru.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -142,10 +133,7 @@ func (aru *AuthRolesUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authroles.TokenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -158,10 +146,7 @@ func (aru *AuthRolesUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authroles.TokenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -233,6 +218,12 @@ func (aruo *AuthRolesUpdateOne) ClearToken() *AuthRolesUpdateOne {
return aruo
}
// Where appends a list predicates to the AuthRolesUpdate builder.
func (aruo *AuthRolesUpdateOne) Where(ps ...predicate.AuthRoles) *AuthRolesUpdateOne {
aruo.mutation.Where(ps...)
return aruo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (aruo *AuthRolesUpdateOne) Select(field string, fields ...string) *AuthRolesUpdateOne {
@@ -281,16 +272,7 @@ func (aruo *AuthRolesUpdateOne) sqlSave(ctx context.Context) (_node *AuthRoles,
if err := aruo.check(); err != nil {
return _node, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: authroles.Table,
Columns: authroles.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(authroles.Table, authroles.Columns, sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt))
id, ok := aruo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AuthRoles.id" for update`)}
@@ -326,10 +308,7 @@ func (aruo *AuthRolesUpdateOne) sqlSave(ctx context.Context) (_node *AuthRoles,
Columns: []string{authroles.TokenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -342,10 +321,7 @@ func (aruo *AuthRolesUpdateOne) sqlSave(ctx context.Context) (_node *AuthRoles,
Columns: []string{authroles.TokenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -142,19 +142,19 @@ func (at *AuthTokens) assignValues(columns []string, values []any) error {
// QueryUser queries the "user" edge of the AuthTokens entity.
func (at *AuthTokens) QueryUser() *UserQuery {
return (&AuthTokensClient{config: at.config}).QueryUser(at)
return NewAuthTokensClient(at.config).QueryUser(at)
}
// QueryRoles queries the "roles" edge of the AuthTokens entity.
func (at *AuthTokens) QueryRoles() *AuthRolesQuery {
return (&AuthTokensClient{config: at.config}).QueryRoles(at)
return NewAuthTokensClient(at.config).QueryRoles(at)
}
// Update returns a builder for updating this AuthTokens.
// Note that you need to call AuthTokens.Unwrap() before calling this method if this AuthTokens
// was returned from a transaction, and the transaction was committed or rolled back.
func (at *AuthTokens) Update() *AuthTokensUpdateOne {
return (&AuthTokensClient{config: at.config}).UpdateOne(at)
return NewAuthTokensClient(at.config).UpdateOne(at)
}
// Unwrap unwraps the AuthTokens entity that was returned from a transaction after it was closed,
@@ -190,9 +190,3 @@ func (at *AuthTokens) String() string {
// AuthTokensSlice is a parsable slice of AuthTokens.
type AuthTokensSlice []*AuthTokens
func (at AuthTokensSlice) config(cfg config) {
for _i := range at {
at[_i].config = cfg
}
}

View File

@@ -219,13 +219,7 @@ func (atc *AuthTokensCreate) sqlSave(ctx context.Context) (*AuthTokens, error) {
func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
var (
_node = &AuthTokens{config: atc.config}
_spec = &sqlgraph.CreateSpec{
Table: authtokens.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
}
_spec = sqlgraph.NewCreateSpec(authtokens.Table, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
)
if id, ok := atc.mutation.ID(); ok {
_node.ID = id
@@ -255,10 +249,7 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -275,10 +266,7 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
for _, k := range nodes {

View File

@@ -40,15 +40,7 @@ func (atd *AuthTokensDelete) ExecX(ctx context.Context) int {
}
func (atd *AuthTokensDelete) sqlExec(ctx context.Context) (int, error) {
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
}
_spec := sqlgraph.NewDeleteSpec(authtokens.Table, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
if ps := atd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -69,6 +61,12 @@ type AuthTokensDeleteOne struct {
atd *AuthTokensDelete
}
// Where appends a list predicates to the AuthTokensDelete builder.
func (atdo *AuthTokensDeleteOne) Where(ps ...predicate.AuthTokens) *AuthTokensDeleteOne {
atdo.atd.mutation.Where(ps...)
return atdo
}
// Exec executes the deletion query.
func (atdo *AuthTokensDeleteOne) Exec(ctx context.Context) error {
n, err := atdo.atd.Exec(ctx)
@@ -84,5 +82,7 @@ func (atdo *AuthTokensDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (atdo *AuthTokensDeleteOne) ExecX(ctx context.Context) {
atdo.atd.ExecX(ctx)
if err := atdo.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -21,11 +21,8 @@ import (
// AuthTokensQuery is the builder for querying AuthTokens entities.
type AuthTokensQuery struct {
config
limit *int
offset *int
unique *bool
ctx *QueryContext
order []OrderFunc
fields []string
inters []Interceptor
predicates []predicate.AuthTokens
withUser *UserQuery
@@ -44,20 +41,20 @@ func (atq *AuthTokensQuery) Where(ps ...predicate.AuthTokens) *AuthTokensQuery {
// Limit the number of records to be returned by this query.
func (atq *AuthTokensQuery) Limit(limit int) *AuthTokensQuery {
atq.limit = &limit
atq.ctx.Limit = &limit
return atq
}
// Offset to start from.
func (atq *AuthTokensQuery) Offset(offset int) *AuthTokensQuery {
atq.offset = &offset
atq.ctx.Offset = &offset
return atq
}
// 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 (atq *AuthTokensQuery) Unique(unique bool) *AuthTokensQuery {
atq.unique = &unique
atq.ctx.Unique = &unique
return atq
}
@@ -114,7 +111,7 @@ func (atq *AuthTokensQuery) QueryRoles() *AuthRolesQuery {
// First returns the first AuthTokens entity from the query.
// Returns a *NotFoundError when no AuthTokens was found.
func (atq *AuthTokensQuery) First(ctx context.Context) (*AuthTokens, error) {
nodes, err := atq.Limit(1).All(newQueryContext(ctx, TypeAuthTokens, "First"))
nodes, err := atq.Limit(1).All(setContextOp(ctx, atq.ctx, "First"))
if err != nil {
return nil, err
}
@@ -137,7 +134,7 @@ func (atq *AuthTokensQuery) FirstX(ctx context.Context) *AuthTokens {
// Returns a *NotFoundError when no AuthTokens ID was found.
func (atq *AuthTokensQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = atq.Limit(1).IDs(newQueryContext(ctx, TypeAuthTokens, "FirstID")); err != nil {
if ids, err = atq.Limit(1).IDs(setContextOp(ctx, atq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
@@ -160,7 +157,7 @@ func (atq *AuthTokensQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one AuthTokens entity is found.
// Returns a *NotFoundError when no AuthTokens entities are found.
func (atq *AuthTokensQuery) Only(ctx context.Context) (*AuthTokens, error) {
nodes, err := atq.Limit(2).All(newQueryContext(ctx, TypeAuthTokens, "Only"))
nodes, err := atq.Limit(2).All(setContextOp(ctx, atq.ctx, "Only"))
if err != nil {
return nil, err
}
@@ -188,7 +185,7 @@ func (atq *AuthTokensQuery) OnlyX(ctx context.Context) *AuthTokens {
// Returns a *NotFoundError when no entities are found.
func (atq *AuthTokensQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = atq.Limit(2).IDs(newQueryContext(ctx, TypeAuthTokens, "OnlyID")); err != nil {
if ids, err = atq.Limit(2).IDs(setContextOp(ctx, atq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
@@ -213,7 +210,7 @@ func (atq *AuthTokensQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of AuthTokensSlice.
func (atq *AuthTokensQuery) All(ctx context.Context) ([]*AuthTokens, error) {
ctx = newQueryContext(ctx, TypeAuthTokens, "All")
ctx = setContextOp(ctx, atq.ctx, "All")
if err := atq.prepareQuery(ctx); err != nil {
return nil, err
}
@@ -231,10 +228,12 @@ func (atq *AuthTokensQuery) AllX(ctx context.Context) []*AuthTokens {
}
// IDs executes the query and returns a list of AuthTokens IDs.
func (atq *AuthTokensQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
ctx = newQueryContext(ctx, TypeAuthTokens, "IDs")
if err := atq.Select(authtokens.FieldID).Scan(ctx, &ids); err != nil {
func (atq *AuthTokensQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if atq.ctx.Unique == nil && atq.path != nil {
atq.Unique(true)
}
ctx = setContextOp(ctx, atq.ctx, "IDs")
if err = atq.Select(authtokens.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@@ -251,7 +250,7 @@ func (atq *AuthTokensQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (atq *AuthTokensQuery) Count(ctx context.Context) (int, error) {
ctx = newQueryContext(ctx, TypeAuthTokens, "Count")
ctx = setContextOp(ctx, atq.ctx, "Count")
if err := atq.prepareQuery(ctx); err != nil {
return 0, err
}
@@ -269,7 +268,7 @@ func (atq *AuthTokensQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (atq *AuthTokensQuery) Exist(ctx context.Context) (bool, error) {
ctx = newQueryContext(ctx, TypeAuthTokens, "Exist")
ctx = setContextOp(ctx, atq.ctx, "Exist")
switch _, err := atq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
@@ -297,17 +296,15 @@ func (atq *AuthTokensQuery) Clone() *AuthTokensQuery {
}
return &AuthTokensQuery{
config: atq.config,
limit: atq.limit,
offset: atq.offset,
ctx: atq.ctx.Clone(),
order: append([]OrderFunc{}, atq.order...),
inters: append([]Interceptor{}, atq.inters...),
predicates: append([]predicate.AuthTokens{}, atq.predicates...),
withUser: atq.withUser.Clone(),
withRoles: atq.withRoles.Clone(),
// clone intermediate query.
sql: atq.sql.Clone(),
path: atq.path,
unique: atq.unique,
sql: atq.sql.Clone(),
path: atq.path,
}
}
@@ -348,9 +345,9 @@ func (atq *AuthTokensQuery) WithRoles(opts ...func(*AuthRolesQuery)) *AuthTokens
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (atq *AuthTokensQuery) GroupBy(field string, fields ...string) *AuthTokensGroupBy {
atq.fields = append([]string{field}, fields...)
atq.ctx.Fields = append([]string{field}, fields...)
grbuild := &AuthTokensGroupBy{build: atq}
grbuild.flds = &atq.fields
grbuild.flds = &atq.ctx.Fields
grbuild.label = authtokens.Label
grbuild.scan = grbuild.Scan
return grbuild
@@ -369,10 +366,10 @@ func (atq *AuthTokensQuery) GroupBy(field string, fields ...string) *AuthTokensG
// Select(authtokens.FieldCreatedAt).
// Scan(ctx, &v)
func (atq *AuthTokensQuery) Select(fields ...string) *AuthTokensSelect {
atq.fields = append(atq.fields, fields...)
atq.ctx.Fields = append(atq.ctx.Fields, fields...)
sbuild := &AuthTokensSelect{AuthTokensQuery: atq}
sbuild.label = authtokens.Label
sbuild.flds, sbuild.scan = &atq.fields, sbuild.Scan
sbuild.flds, sbuild.scan = &atq.ctx.Fields, sbuild.Scan
return sbuild
}
@@ -392,7 +389,7 @@ func (atq *AuthTokensQuery) prepareQuery(ctx context.Context) error {
}
}
}
for _, f := range atq.fields {
for _, f := range atq.ctx.Fields {
if !authtokens.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@@ -469,6 +466,9 @@ func (atq *AuthTokensQuery) loadUser(ctx context.Context, query *UserQuery, node
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(user.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@@ -516,30 +516,22 @@ func (atq *AuthTokensQuery) loadRoles(ctx context.Context, query *AuthRolesQuery
func (atq *AuthTokensQuery) sqlCount(ctx context.Context) (int, error) {
_spec := atq.querySpec()
_spec.Node.Columns = atq.fields
if len(atq.fields) > 0 {
_spec.Unique = atq.unique != nil && *atq.unique
_spec.Node.Columns = atq.ctx.Fields
if len(atq.ctx.Fields) > 0 {
_spec.Unique = atq.ctx.Unique != nil && *atq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, atq.driver, _spec)
}
func (atq *AuthTokensQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
Columns: authtokens.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
From: atq.sql,
Unique: true,
}
if unique := atq.unique; unique != nil {
_spec := sqlgraph.NewQuerySpec(authtokens.Table, authtokens.Columns, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
_spec.From = atq.sql
if unique := atq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if atq.path != nil {
_spec.Unique = true
}
if fields := atq.fields; len(fields) > 0 {
if fields := atq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, authtokens.FieldID)
for i := range fields {
@@ -555,10 +547,10 @@ func (atq *AuthTokensQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := atq.limit; limit != nil {
if limit := atq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := atq.offset; offset != nil {
if offset := atq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := atq.order; len(ps) > 0 {
@@ -574,7 +566,7 @@ func (atq *AuthTokensQuery) querySpec() *sqlgraph.QuerySpec {
func (atq *AuthTokensQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(atq.driver.Dialect())
t1 := builder.Table(authtokens.Table)
columns := atq.fields
columns := atq.ctx.Fields
if len(columns) == 0 {
columns = authtokens.Columns
}
@@ -583,7 +575,7 @@ func (atq *AuthTokensQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = atq.sql
selector.Select(selector.Columns(columns...)...)
}
if atq.unique != nil && *atq.unique {
if atq.ctx.Unique != nil && *atq.ctx.Unique {
selector.Distinct()
}
for _, p := range atq.predicates {
@@ -592,12 +584,12 @@ func (atq *AuthTokensQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range atq.order {
p(selector)
}
if offset := atq.offset; offset != nil {
if offset := atq.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 := atq.limit; limit != nil {
if limit := atq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
@@ -617,7 +609,7 @@ func (atgb *AuthTokensGroupBy) Aggregate(fns ...AggregateFunc) *AuthTokensGroupB
// Scan applies the selector query and scans the result into the given value.
func (atgb *AuthTokensGroupBy) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeAuthTokens, "GroupBy")
ctx = setContextOp(ctx, atgb.build.ctx, "GroupBy")
if err := atgb.build.prepareQuery(ctx); err != nil {
return err
}
@@ -665,7 +657,7 @@ func (ats *AuthTokensSelect) Aggregate(fns ...AggregateFunc) *AuthTokensSelect {
// Scan applies the selector query and scans the result into the given value.
func (ats *AuthTokensSelect) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeAuthTokens, "Select")
ctx = setContextOp(ctx, ats.ctx, "Select")
if err := ats.prepareQuery(ctx); err != nil {
return err
}

View File

@@ -149,16 +149,7 @@ func (atu *AuthTokensUpdate) defaults() {
}
func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
Columns: authtokens.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(authtokens.Table, authtokens.Columns, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
if ps := atu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -183,10 +174,7 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -199,10 +187,7 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -218,10 +203,7 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -234,10 +216,7 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
@@ -346,6 +325,12 @@ func (atuo *AuthTokensUpdateOne) ClearRoles() *AuthTokensUpdateOne {
return atuo
}
// Where appends a list predicates to the AuthTokensUpdate builder.
func (atuo *AuthTokensUpdateOne) Where(ps ...predicate.AuthTokens) *AuthTokensUpdateOne {
atuo.mutation.Where(ps...)
return atuo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (atuo *AuthTokensUpdateOne) Select(field string, fields ...string) *AuthTokensUpdateOne {
@@ -390,16 +375,7 @@ func (atuo *AuthTokensUpdateOne) defaults() {
}
func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens, err error) {
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
Columns: authtokens.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(authtokens.Table, authtokens.Columns, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
id, ok := atuo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AuthTokens.id" for update`)}
@@ -441,10 +417,7 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -457,10 +430,7 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -476,10 +446,7 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -492,10 +459,7 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
for _, k := range nodes {

View File

@@ -11,6 +11,10 @@ import (
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/migrate"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
@@ -22,11 +26,8 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
// Client is the client that holds all ent builders.
@@ -56,6 +57,8 @@ type Client struct {
Location *LocationClient
// MaintenanceEntry is the client for interacting with the MaintenanceEntry builders.
MaintenanceEntry *MaintenanceEntryClient
// Notifier is the client for interacting with the Notifier builders.
Notifier *NotifierClient
// User is the client for interacting with the User builders.
User *UserClient
}
@@ -82,9 +85,59 @@ func (c *Client) init() {
c.Label = NewLabelClient(c.config)
c.Location = NewLocationClient(c.config)
c.MaintenanceEntry = NewMaintenanceEntryClient(c.config)
c.Notifier = NewNotifierClient(c.config)
c.User = NewUserClient(c.config)
}
type (
// config is the configuration for the client and its builder.
config struct {
// driver used for executing database requests.
driver dialect.Driver
// debug enable a debug logging.
debug bool
// log used for logging on debug mode.
log func(...any)
// hooks to execute on mutations.
hooks *hooks
// interceptors to execute on queries.
inters *inters
}
// Option function to configure the client.
Option func(*config)
)
// options applies the options on the config object.
func (c *config) options(opts ...Option) {
for _, opt := range opts {
opt(c)
}
if c.debug {
c.driver = dialect.Debug(c.driver, c.log)
}
}
// Debug enables debug logging on the ent.Driver.
func Debug() Option {
return func(c *config) {
c.debug = true
}
}
// Log sets the logging function for debug mode.
func Log(fn func(...any)) Option {
return func(c *config) {
c.log = fn
}
}
// Driver configures the client driver.
func Driver(driver dialect.Driver) Option {
return func(c *config) {
c.driver = driver
}
}
// Open opens a database/sql.DB specified by the driver name and
// the data source name, and returns a new client attached to it.
// Optional parameters can be added for configuring the client.
@@ -127,6 +180,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
Label: NewLabelClient(cfg),
Location: NewLocationClient(cfg),
MaintenanceEntry: NewMaintenanceEntryClient(cfg),
Notifier: NewNotifierClient(cfg),
User: NewUserClient(cfg),
}, nil
}
@@ -158,6 +212,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
Label: NewLabelClient(cfg),
Location: NewLocationClient(cfg),
MaintenanceEntry: NewMaintenanceEntryClient(cfg),
Notifier: NewNotifierClient(cfg),
User: NewUserClient(cfg),
}, nil
}
@@ -187,35 +242,25 @@ func (c *Client) Close() error {
// Use adds the mutation hooks to all the entity clients.
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) {
c.Attachment.Use(hooks...)
c.AuthRoles.Use(hooks...)
c.AuthTokens.Use(hooks...)
c.Document.Use(hooks...)
c.Group.Use(hooks...)
c.GroupInvitationToken.Use(hooks...)
c.Item.Use(hooks...)
c.ItemField.Use(hooks...)
c.Label.Use(hooks...)
c.Location.Use(hooks...)
c.MaintenanceEntry.Use(hooks...)
c.User.Use(hooks...)
for _, n := range []interface{ Use(...Hook) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group,
c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location,
c.MaintenanceEntry, c.Notifier, c.User,
} {
n.Use(hooks...)
}
}
// Intercept adds the query interceptors to all the entity clients.
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) {
c.Attachment.Intercept(interceptors...)
c.AuthRoles.Intercept(interceptors...)
c.AuthTokens.Intercept(interceptors...)
c.Document.Intercept(interceptors...)
c.Group.Intercept(interceptors...)
c.GroupInvitationToken.Intercept(interceptors...)
c.Item.Intercept(interceptors...)
c.ItemField.Intercept(interceptors...)
c.Label.Intercept(interceptors...)
c.Location.Intercept(interceptors...)
c.MaintenanceEntry.Intercept(interceptors...)
c.User.Intercept(interceptors...)
for _, n := range []interface{ Intercept(...Interceptor) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group,
c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location,
c.MaintenanceEntry, c.Notifier, c.User,
} {
n.Intercept(interceptors...)
}
}
// Mutate implements the ent.Mutator interface.
@@ -243,6 +288,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.Location.mutate(ctx, m)
case *MaintenanceEntryMutation:
return c.MaintenanceEntry.mutate(ctx, m)
case *NotifierMutation:
return c.Notifier.mutate(ctx, m)
case *UserMutation:
return c.User.mutate(ctx, m)
default:
@@ -266,7 +313,7 @@ func (c *AttachmentClient) Use(hooks ...Hook) {
c.hooks.Attachment = append(c.hooks.Attachment, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `attachment.Intercept(f(g(h())))`.
func (c *AttachmentClient) Intercept(interceptors ...Interceptor) {
c.inters.Attachment = append(c.inters.Attachment, interceptors...)
@@ -324,6 +371,7 @@ func (c *AttachmentClient) DeleteOneID(id uuid.UUID) *AttachmentDeleteOne {
func (c *AttachmentClient) Query() *AttachmentQuery {
return &AttachmentQuery{
config: c.config,
ctx: &QueryContext{Type: TypeAttachment},
inters: c.Interceptors(),
}
}
@@ -415,7 +463,7 @@ func (c *AuthRolesClient) Use(hooks ...Hook) {
c.hooks.AuthRoles = append(c.hooks.AuthRoles, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `authroles.Intercept(f(g(h())))`.
func (c *AuthRolesClient) Intercept(interceptors ...Interceptor) {
c.inters.AuthRoles = append(c.inters.AuthRoles, interceptors...)
@@ -473,6 +521,7 @@ func (c *AuthRolesClient) DeleteOneID(id int) *AuthRolesDeleteOne {
func (c *AuthRolesClient) Query() *AuthRolesQuery {
return &AuthRolesQuery{
config: c.config,
ctx: &QueryContext{Type: TypeAuthRoles},
inters: c.Interceptors(),
}
}
@@ -548,7 +597,7 @@ func (c *AuthTokensClient) Use(hooks ...Hook) {
c.hooks.AuthTokens = append(c.hooks.AuthTokens, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `authtokens.Intercept(f(g(h())))`.
func (c *AuthTokensClient) Intercept(interceptors ...Interceptor) {
c.inters.AuthTokens = append(c.inters.AuthTokens, interceptors...)
@@ -606,6 +655,7 @@ func (c *AuthTokensClient) DeleteOneID(id uuid.UUID) *AuthTokensDeleteOne {
func (c *AuthTokensClient) Query() *AuthTokensQuery {
return &AuthTokensQuery{
config: c.config,
ctx: &QueryContext{Type: TypeAuthTokens},
inters: c.Interceptors(),
}
}
@@ -697,7 +747,7 @@ func (c *DocumentClient) Use(hooks ...Hook) {
c.hooks.Document = append(c.hooks.Document, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `document.Intercept(f(g(h())))`.
func (c *DocumentClient) Intercept(interceptors ...Interceptor) {
c.inters.Document = append(c.inters.Document, interceptors...)
@@ -755,6 +805,7 @@ func (c *DocumentClient) DeleteOneID(id uuid.UUID) *DocumentDeleteOne {
func (c *DocumentClient) Query() *DocumentQuery {
return &DocumentQuery{
config: c.config,
ctx: &QueryContext{Type: TypeDocument},
inters: c.Interceptors(),
}
}
@@ -846,7 +897,7 @@ func (c *GroupClient) Use(hooks ...Hook) {
c.hooks.Group = append(c.hooks.Group, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `group.Intercept(f(g(h())))`.
func (c *GroupClient) Intercept(interceptors ...Interceptor) {
c.inters.Group = append(c.inters.Group, interceptors...)
@@ -904,6 +955,7 @@ func (c *GroupClient) DeleteOneID(id uuid.UUID) *GroupDeleteOne {
func (c *GroupClient) Query() *GroupQuery {
return &GroupQuery{
config: c.config,
ctx: &QueryContext{Type: TypeGroup},
inters: c.Interceptors(),
}
}
@@ -1018,6 +1070,22 @@ func (c *GroupClient) QueryInvitationTokens(gr *Group) *GroupInvitationTokenQuer
return query
}
// QueryNotifiers queries the notifiers edge of a Group.
func (c *GroupClient) QueryNotifiers(gr *Group) *NotifierQuery {
query := (&NotifierClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := gr.ID
step := sqlgraph.NewStep(
sqlgraph.From(group.Table, group.FieldID, id),
sqlgraph.To(notifier.Table, notifier.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.NotifiersTable, group.NotifiersColumn),
)
fromV = sqlgraph.Neighbors(gr.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *GroupClient) Hooks() []Hook {
return c.hooks.Group
@@ -1059,7 +1127,7 @@ func (c *GroupInvitationTokenClient) Use(hooks ...Hook) {
c.hooks.GroupInvitationToken = append(c.hooks.GroupInvitationToken, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `groupinvitationtoken.Intercept(f(g(h())))`.
func (c *GroupInvitationTokenClient) Intercept(interceptors ...Interceptor) {
c.inters.GroupInvitationToken = append(c.inters.GroupInvitationToken, interceptors...)
@@ -1117,6 +1185,7 @@ func (c *GroupInvitationTokenClient) DeleteOneID(id uuid.UUID) *GroupInvitationT
func (c *GroupInvitationTokenClient) Query() *GroupInvitationTokenQuery {
return &GroupInvitationTokenQuery{
config: c.config,
ctx: &QueryContext{Type: TypeGroupInvitationToken},
inters: c.Interceptors(),
}
}
@@ -1192,7 +1261,7 @@ func (c *ItemClient) Use(hooks ...Hook) {
c.hooks.Item = append(c.hooks.Item, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `item.Intercept(f(g(h())))`.
func (c *ItemClient) Intercept(interceptors ...Interceptor) {
c.inters.Item = append(c.inters.Item, interceptors...)
@@ -1250,6 +1319,7 @@ func (c *ItemClient) DeleteOneID(id uuid.UUID) *ItemDeleteOne {
func (c *ItemClient) Query() *ItemQuery {
return &ItemQuery{
config: c.config,
ctx: &QueryContext{Type: TypeItem},
inters: c.Interceptors(),
}
}
@@ -1268,6 +1338,22 @@ func (c *ItemClient) GetX(ctx context.Context, id uuid.UUID) *Item {
return obj
}
// QueryGroup queries the group edge of a Item.
func (c *ItemClient) QueryGroup(i *Item) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := i.ID
step := sqlgraph.NewStep(
sqlgraph.From(item.Table, item.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, item.GroupTable, item.GroupColumn),
)
fromV = sqlgraph.Neighbors(i.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryParent queries the parent edge of a Item.
func (c *ItemClient) QueryParent(i *Item) *ItemQuery {
query := (&ItemClient{config: c.config}).Query()
@@ -1300,22 +1386,6 @@ func (c *ItemClient) QueryChildren(i *Item) *ItemQuery {
return query
}
// QueryGroup queries the group edge of a Item.
func (c *ItemClient) QueryGroup(i *Item) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := i.ID
step := sqlgraph.NewStep(
sqlgraph.From(item.Table, item.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, item.GroupTable, item.GroupColumn),
)
fromV = sqlgraph.Neighbors(i.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryLabel queries the label edge of a Item.
func (c *ItemClient) QueryLabel(i *Item) *LabelQuery {
query := (&LabelClient{config: c.config}).Query()
@@ -1437,7 +1507,7 @@ func (c *ItemFieldClient) Use(hooks ...Hook) {
c.hooks.ItemField = append(c.hooks.ItemField, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `itemfield.Intercept(f(g(h())))`.
func (c *ItemFieldClient) Intercept(interceptors ...Interceptor) {
c.inters.ItemField = append(c.inters.ItemField, interceptors...)
@@ -1495,6 +1565,7 @@ func (c *ItemFieldClient) DeleteOneID(id uuid.UUID) *ItemFieldDeleteOne {
func (c *ItemFieldClient) Query() *ItemFieldQuery {
return &ItemFieldQuery{
config: c.config,
ctx: &QueryContext{Type: TypeItemField},
inters: c.Interceptors(),
}
}
@@ -1570,7 +1641,7 @@ func (c *LabelClient) Use(hooks ...Hook) {
c.hooks.Label = append(c.hooks.Label, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `label.Intercept(f(g(h())))`.
func (c *LabelClient) Intercept(interceptors ...Interceptor) {
c.inters.Label = append(c.inters.Label, interceptors...)
@@ -1628,6 +1699,7 @@ func (c *LabelClient) DeleteOneID(id uuid.UUID) *LabelDeleteOne {
func (c *LabelClient) Query() *LabelQuery {
return &LabelQuery{
config: c.config,
ctx: &QueryContext{Type: TypeLabel},
inters: c.Interceptors(),
}
}
@@ -1719,7 +1791,7 @@ func (c *LocationClient) Use(hooks ...Hook) {
c.hooks.Location = append(c.hooks.Location, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `location.Intercept(f(g(h())))`.
func (c *LocationClient) Intercept(interceptors ...Interceptor) {
c.inters.Location = append(c.inters.Location, interceptors...)
@@ -1777,6 +1849,7 @@ func (c *LocationClient) DeleteOneID(id uuid.UUID) *LocationDeleteOne {
func (c *LocationClient) Query() *LocationQuery {
return &LocationQuery{
config: c.config,
ctx: &QueryContext{Type: TypeLocation},
inters: c.Interceptors(),
}
}
@@ -1795,6 +1868,22 @@ func (c *LocationClient) GetX(ctx context.Context, id uuid.UUID) *Location {
return obj
}
// QueryGroup queries the group edge of a Location.
func (c *LocationClient) QueryGroup(l *Location) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := l.ID
step := sqlgraph.NewStep(
sqlgraph.From(location.Table, location.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, location.GroupTable, location.GroupColumn),
)
fromV = sqlgraph.Neighbors(l.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryParent queries the parent edge of a Location.
func (c *LocationClient) QueryParent(l *Location) *LocationQuery {
query := (&LocationClient{config: c.config}).Query()
@@ -1827,22 +1916,6 @@ func (c *LocationClient) QueryChildren(l *Location) *LocationQuery {
return query
}
// QueryGroup queries the group edge of a Location.
func (c *LocationClient) QueryGroup(l *Location) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := l.ID
step := sqlgraph.NewStep(
sqlgraph.From(location.Table, location.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, location.GroupTable, location.GroupColumn),
)
fromV = sqlgraph.Neighbors(l.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryItems queries the items edge of a Location.
func (c *LocationClient) QueryItems(l *Location) *ItemQuery {
query := (&ItemClient{config: c.config}).Query()
@@ -1900,7 +1973,7 @@ func (c *MaintenanceEntryClient) Use(hooks ...Hook) {
c.hooks.MaintenanceEntry = append(c.hooks.MaintenanceEntry, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `maintenanceentry.Intercept(f(g(h())))`.
func (c *MaintenanceEntryClient) Intercept(interceptors ...Interceptor) {
c.inters.MaintenanceEntry = append(c.inters.MaintenanceEntry, interceptors...)
@@ -1958,6 +2031,7 @@ func (c *MaintenanceEntryClient) DeleteOneID(id uuid.UUID) *MaintenanceEntryDele
func (c *MaintenanceEntryClient) Query() *MaintenanceEntryQuery {
return &MaintenanceEntryQuery{
config: c.config,
ctx: &QueryContext{Type: TypeMaintenanceEntry},
inters: c.Interceptors(),
}
}
@@ -2017,6 +2091,156 @@ func (c *MaintenanceEntryClient) mutate(ctx context.Context, m *MaintenanceEntry
}
}
// NotifierClient is a client for the Notifier schema.
type NotifierClient struct {
config
}
// NewNotifierClient returns a client for the Notifier from the given config.
func NewNotifierClient(c config) *NotifierClient {
return &NotifierClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `notifier.Hooks(f(g(h())))`.
func (c *NotifierClient) Use(hooks ...Hook) {
c.hooks.Notifier = append(c.hooks.Notifier, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `notifier.Intercept(f(g(h())))`.
func (c *NotifierClient) Intercept(interceptors ...Interceptor) {
c.inters.Notifier = append(c.inters.Notifier, interceptors...)
}
// Create returns a builder for creating a Notifier entity.
func (c *NotifierClient) Create() *NotifierCreate {
mutation := newNotifierMutation(c.config, OpCreate)
return &NotifierCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of Notifier entities.
func (c *NotifierClient) CreateBulk(builders ...*NotifierCreate) *NotifierCreateBulk {
return &NotifierCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Notifier.
func (c *NotifierClient) Update() *NotifierUpdate {
mutation := newNotifierMutation(c.config, OpUpdate)
return &NotifierUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *NotifierClient) UpdateOne(n *Notifier) *NotifierUpdateOne {
mutation := newNotifierMutation(c.config, OpUpdateOne, withNotifier(n))
return &NotifierUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *NotifierClient) UpdateOneID(id uuid.UUID) *NotifierUpdateOne {
mutation := newNotifierMutation(c.config, OpUpdateOne, withNotifierID(id))
return &NotifierUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for Notifier.
func (c *NotifierClient) Delete() *NotifierDelete {
mutation := newNotifierMutation(c.config, OpDelete)
return &NotifierDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *NotifierClient) DeleteOne(n *Notifier) *NotifierDeleteOne {
return c.DeleteOneID(n.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *NotifierClient) DeleteOneID(id uuid.UUID) *NotifierDeleteOne {
builder := c.Delete().Where(notifier.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &NotifierDeleteOne{builder}
}
// Query returns a query builder for Notifier.
func (c *NotifierClient) Query() *NotifierQuery {
return &NotifierQuery{
config: c.config,
ctx: &QueryContext{Type: TypeNotifier},
inters: c.Interceptors(),
}
}
// Get returns a Notifier entity by its id.
func (c *NotifierClient) Get(ctx context.Context, id uuid.UUID) (*Notifier, error) {
return c.Query().Where(notifier.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *NotifierClient) GetX(ctx context.Context, id uuid.UUID) *Notifier {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryGroup queries the group edge of a Notifier.
func (c *NotifierClient) QueryGroup(n *Notifier) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := n.ID
step := sqlgraph.NewStep(
sqlgraph.From(notifier.Table, notifier.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, notifier.GroupTable, notifier.GroupColumn),
)
fromV = sqlgraph.Neighbors(n.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryUser queries the user edge of a Notifier.
func (c *NotifierClient) QueryUser(n *Notifier) *UserQuery {
query := (&UserClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := n.ID
step := sqlgraph.NewStep(
sqlgraph.From(notifier.Table, notifier.FieldID, id),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, notifier.UserTable, notifier.UserColumn),
)
fromV = sqlgraph.Neighbors(n.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *NotifierClient) Hooks() []Hook {
return c.hooks.Notifier
}
// Interceptors returns the client interceptors.
func (c *NotifierClient) Interceptors() []Interceptor {
return c.inters.Notifier
}
func (c *NotifierClient) mutate(ctx context.Context, m *NotifierMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&NotifierCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&NotifierUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&NotifierUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&NotifierDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown Notifier mutation op: %q", m.Op())
}
}
// UserClient is a client for the User schema.
type UserClient struct {
config
@@ -2033,7 +2257,7 @@ func (c *UserClient) Use(hooks ...Hook) {
c.hooks.User = append(c.hooks.User, hooks...)
}
// Use adds a list of query interceptors to the interceptors stack.
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `user.Intercept(f(g(h())))`.
func (c *UserClient) Intercept(interceptors ...Interceptor) {
c.inters.User = append(c.inters.User, interceptors...)
@@ -2091,6 +2315,7 @@ func (c *UserClient) DeleteOneID(id uuid.UUID) *UserDeleteOne {
func (c *UserClient) Query() *UserQuery {
return &UserQuery{
config: c.config,
ctx: &QueryContext{Type: TypeUser},
inters: c.Interceptors(),
}
}
@@ -2141,6 +2366,22 @@ func (c *UserClient) QueryAuthTokens(u *User) *AuthTokensQuery {
return query
}
// QueryNotifiers queries the notifiers edge of a User.
func (c *UserClient) QueryNotifiers(u *User) *NotifierQuery {
query := (&NotifierClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := u.ID
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, id),
sqlgraph.To(notifier.Table, notifier.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.NotifiersTable, user.NotifiersColumn),
)
fromV = sqlgraph.Neighbors(u.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *UserClient) Hooks() []Hook {
return c.hooks.User
@@ -2165,3 +2406,15 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error)
return nil, fmt.Errorf("ent: unknown User mutation op: %q", m.Op())
}
}
// hooks and interceptors per client, for fast access.
type (
hooks struct {
Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item,
ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Hook
}
inters struct {
Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item,
ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Interceptor
}
)

View File

@@ -1,88 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"entgo.io/ent"
"entgo.io/ent/dialect"
)
// Option function to configure the client.
type Option func(*config)
// Config is the configuration for the client and its builder.
type config struct {
// driver used for executing database requests.
driver dialect.Driver
// debug enable a debug logging.
debug bool
// log used for logging on debug mode.
log func(...any)
// hooks to execute on mutations.
hooks *hooks
// interceptors to execute on queries.
inters *inters
}
// hooks and interceptors per client, for fast access.
type (
hooks struct {
Attachment []ent.Hook
AuthRoles []ent.Hook
AuthTokens []ent.Hook
Document []ent.Hook
Group []ent.Hook
GroupInvitationToken []ent.Hook
Item []ent.Hook
ItemField []ent.Hook
Label []ent.Hook
Location []ent.Hook
MaintenanceEntry []ent.Hook
User []ent.Hook
}
inters struct {
Attachment []ent.Interceptor
AuthRoles []ent.Interceptor
AuthTokens []ent.Interceptor
Document []ent.Interceptor
Group []ent.Interceptor
GroupInvitationToken []ent.Interceptor
Item []ent.Interceptor
ItemField []ent.Interceptor
Label []ent.Interceptor
Location []ent.Interceptor
MaintenanceEntry []ent.Interceptor
User []ent.Interceptor
}
)
// Options applies the options on the config object.
func (c *config) options(opts ...Option) {
for _, opt := range opts {
opt(c)
}
if c.debug {
c.driver = dialect.Debug(c.driver, c.log)
}
}
// Debug enables debug logging on the ent.Driver.
func Debug() Option {
return func(c *config) {
c.debug = true
}
}
// Log sets the logging function for debug mode.
func Log(fn func(...any)) Option {
return func(c *config) {
c.log = fn
}
}
// Driver configures the client driver.
func Driver(driver dialect.Driver) Option {
return func(c *config) {
c.driver = driver
}
}

View File

@@ -1,33 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
)
type clientCtxKey struct{}
// FromContext returns a Client stored inside a context, or nil if there isn't one.
func FromContext(ctx context.Context) *Client {
c, _ := ctx.Value(clientCtxKey{}).(*Client)
return c
}
// NewContext returns a new context with the given Client attached.
func NewContext(parent context.Context, c *Client) context.Context {
return context.WithValue(parent, clientCtxKey{}, c)
}
type txCtxKey struct{}
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
func TxFromContext(ctx context.Context) *Tx {
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
return tx
}
// NewTxContext returns a new context with the given Tx attached.
func NewTxContext(parent context.Context, tx *Tx) context.Context {
return context.WithValue(parent, txCtxKey{}, tx)
}

View File

@@ -137,19 +137,19 @@ func (d *Document) assignValues(columns []string, values []any) error {
// QueryGroup queries the "group" edge of the Document entity.
func (d *Document) QueryGroup() *GroupQuery {
return (&DocumentClient{config: d.config}).QueryGroup(d)
return NewDocumentClient(d.config).QueryGroup(d)
}
// QueryAttachments queries the "attachments" edge of the Document entity.
func (d *Document) QueryAttachments() *AttachmentQuery {
return (&DocumentClient{config: d.config}).QueryAttachments(d)
return NewDocumentClient(d.config).QueryAttachments(d)
}
// Update returns a builder for updating this Document.
// Note that you need to call Document.Unwrap() before calling this method if this Document
// was returned from a transaction, and the transaction was committed or rolled back.
func (d *Document) Update() *DocumentUpdateOne {
return (&DocumentClient{config: d.config}).UpdateOne(d)
return NewDocumentClient(d.config).UpdateOne(d)
}
// Unwrap unwraps the Document entity that was returned from a transaction after it was closed,
@@ -185,9 +185,3 @@ func (d *Document) String() string {
// Documents is a parsable slice of Document.
type Documents []*Document
func (d Documents) config(cfg config) {
for _i := range d {
d[_i].config = cfg
}
}

View File

@@ -208,13 +208,7 @@ func (dc *DocumentCreate) sqlSave(ctx context.Context) (*Document, error) {
func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
var (
_node = &Document{config: dc.config}
_spec = &sqlgraph.CreateSpec{
Table: document.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
}
_spec = sqlgraph.NewCreateSpec(document.Table, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
)
if id, ok := dc.mutation.ID(); ok {
_node.ID = id
@@ -244,10 +238,7 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -264,10 +255,7 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -40,15 +40,7 @@ func (dd *DocumentDelete) ExecX(ctx context.Context) int {
}
func (dd *DocumentDelete) sqlExec(ctx context.Context) (int, error) {
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
_spec := sqlgraph.NewDeleteSpec(document.Table, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
if ps := dd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -69,6 +61,12 @@ type DocumentDeleteOne struct {
dd *DocumentDelete
}
// Where appends a list predicates to the DocumentDelete builder.
func (ddo *DocumentDeleteOne) Where(ps ...predicate.Document) *DocumentDeleteOne {
ddo.dd.mutation.Where(ps...)
return ddo
}
// Exec executes the deletion query.
func (ddo *DocumentDeleteOne) Exec(ctx context.Context) error {
n, err := ddo.dd.Exec(ctx)
@@ -84,5 +82,7 @@ func (ddo *DocumentDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (ddo *DocumentDeleteOne) ExecX(ctx context.Context) {
ddo.dd.ExecX(ctx)
if err := ddo.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -21,11 +21,8 @@ import (
// DocumentQuery is the builder for querying Document entities.
type DocumentQuery struct {
config
limit *int
offset *int
unique *bool
ctx *QueryContext
order []OrderFunc
fields []string
inters []Interceptor
predicates []predicate.Document
withGroup *GroupQuery
@@ -44,20 +41,20 @@ func (dq *DocumentQuery) Where(ps ...predicate.Document) *DocumentQuery {
// Limit the number of records to be returned by this query.
func (dq *DocumentQuery) Limit(limit int) *DocumentQuery {
dq.limit = &limit
dq.ctx.Limit = &limit
return dq
}
// Offset to start from.
func (dq *DocumentQuery) Offset(offset int) *DocumentQuery {
dq.offset = &offset
dq.ctx.Offset = &offset
return dq
}
// 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 (dq *DocumentQuery) Unique(unique bool) *DocumentQuery {
dq.unique = &unique
dq.ctx.Unique = &unique
return dq
}
@@ -114,7 +111,7 @@ func (dq *DocumentQuery) QueryAttachments() *AttachmentQuery {
// First returns the first Document entity from the query.
// Returns a *NotFoundError when no Document was found.
func (dq *DocumentQuery) First(ctx context.Context) (*Document, error) {
nodes, err := dq.Limit(1).All(newQueryContext(ctx, TypeDocument, "First"))
nodes, err := dq.Limit(1).All(setContextOp(ctx, dq.ctx, "First"))
if err != nil {
return nil, err
}
@@ -137,7 +134,7 @@ func (dq *DocumentQuery) FirstX(ctx context.Context) *Document {
// Returns a *NotFoundError when no Document ID was found.
func (dq *DocumentQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dq.Limit(1).IDs(newQueryContext(ctx, TypeDocument, "FirstID")); err != nil {
if ids, err = dq.Limit(1).IDs(setContextOp(ctx, dq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
@@ -160,7 +157,7 @@ func (dq *DocumentQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one Document entity is found.
// Returns a *NotFoundError when no Document entities are found.
func (dq *DocumentQuery) Only(ctx context.Context) (*Document, error) {
nodes, err := dq.Limit(2).All(newQueryContext(ctx, TypeDocument, "Only"))
nodes, err := dq.Limit(2).All(setContextOp(ctx, dq.ctx, "Only"))
if err != nil {
return nil, err
}
@@ -188,7 +185,7 @@ func (dq *DocumentQuery) OnlyX(ctx context.Context) *Document {
// Returns a *NotFoundError when no entities are found.
func (dq *DocumentQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dq.Limit(2).IDs(newQueryContext(ctx, TypeDocument, "OnlyID")); err != nil {
if ids, err = dq.Limit(2).IDs(setContextOp(ctx, dq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
@@ -213,7 +210,7 @@ func (dq *DocumentQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of Documents.
func (dq *DocumentQuery) All(ctx context.Context) ([]*Document, error) {
ctx = newQueryContext(ctx, TypeDocument, "All")
ctx = setContextOp(ctx, dq.ctx, "All")
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
}
@@ -231,10 +228,12 @@ func (dq *DocumentQuery) AllX(ctx context.Context) []*Document {
}
// IDs executes the query and returns a list of Document IDs.
func (dq *DocumentQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
ctx = newQueryContext(ctx, TypeDocument, "IDs")
if err := dq.Select(document.FieldID).Scan(ctx, &ids); err != nil {
func (dq *DocumentQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if dq.ctx.Unique == nil && dq.path != nil {
dq.Unique(true)
}
ctx = setContextOp(ctx, dq.ctx, "IDs")
if err = dq.Select(document.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@@ -251,7 +250,7 @@ func (dq *DocumentQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (dq *DocumentQuery) Count(ctx context.Context) (int, error) {
ctx = newQueryContext(ctx, TypeDocument, "Count")
ctx = setContextOp(ctx, dq.ctx, "Count")
if err := dq.prepareQuery(ctx); err != nil {
return 0, err
}
@@ -269,7 +268,7 @@ func (dq *DocumentQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (dq *DocumentQuery) Exist(ctx context.Context) (bool, error) {
ctx = newQueryContext(ctx, TypeDocument, "Exist")
ctx = setContextOp(ctx, dq.ctx, "Exist")
switch _, err := dq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
@@ -297,17 +296,15 @@ func (dq *DocumentQuery) Clone() *DocumentQuery {
}
return &DocumentQuery{
config: dq.config,
limit: dq.limit,
offset: dq.offset,
ctx: dq.ctx.Clone(),
order: append([]OrderFunc{}, dq.order...),
inters: append([]Interceptor{}, dq.inters...),
predicates: append([]predicate.Document{}, dq.predicates...),
withGroup: dq.withGroup.Clone(),
withAttachments: dq.withAttachments.Clone(),
// clone intermediate query.
sql: dq.sql.Clone(),
path: dq.path,
unique: dq.unique,
sql: dq.sql.Clone(),
path: dq.path,
}
}
@@ -348,9 +345,9 @@ func (dq *DocumentQuery) WithAttachments(opts ...func(*AttachmentQuery)) *Docume
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (dq *DocumentQuery) GroupBy(field string, fields ...string) *DocumentGroupBy {
dq.fields = append([]string{field}, fields...)
dq.ctx.Fields = append([]string{field}, fields...)
grbuild := &DocumentGroupBy{build: dq}
grbuild.flds = &dq.fields
grbuild.flds = &dq.ctx.Fields
grbuild.label = document.Label
grbuild.scan = grbuild.Scan
return grbuild
@@ -369,10 +366,10 @@ func (dq *DocumentQuery) GroupBy(field string, fields ...string) *DocumentGroupB
// Select(document.FieldCreatedAt).
// Scan(ctx, &v)
func (dq *DocumentQuery) Select(fields ...string) *DocumentSelect {
dq.fields = append(dq.fields, fields...)
dq.ctx.Fields = append(dq.ctx.Fields, fields...)
sbuild := &DocumentSelect{DocumentQuery: dq}
sbuild.label = document.Label
sbuild.flds, sbuild.scan = &dq.fields, sbuild.Scan
sbuild.flds, sbuild.scan = &dq.ctx.Fields, sbuild.Scan
return sbuild
}
@@ -392,7 +389,7 @@ func (dq *DocumentQuery) prepareQuery(ctx context.Context) error {
}
}
}
for _, f := range dq.fields {
for _, f := range dq.ctx.Fields {
if !document.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@@ -470,6 +467,9 @@ func (dq *DocumentQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes
}
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 {
@@ -520,30 +520,22 @@ func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQ
func (dq *DocumentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := dq.querySpec()
_spec.Node.Columns = dq.fields
if len(dq.fields) > 0 {
_spec.Unique = dq.unique != nil && *dq.unique
_spec.Node.Columns = dq.ctx.Fields
if len(dq.ctx.Fields) > 0 {
_spec.Unique = dq.ctx.Unique != nil && *dq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, dq.driver, _spec)
}
func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
Columns: document.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
From: dq.sql,
Unique: true,
}
if unique := dq.unique; unique != nil {
_spec := sqlgraph.NewQuerySpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
_spec.From = dq.sql
if unique := dq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if dq.path != nil {
_spec.Unique = true
}
if fields := dq.fields; len(fields) > 0 {
if fields := dq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, document.FieldID)
for i := range fields {
@@ -559,10 +551,10 @@ func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := dq.limit; limit != nil {
if limit := dq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := dq.offset; offset != nil {
if offset := dq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := dq.order; len(ps) > 0 {
@@ -578,7 +570,7 @@ func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(dq.driver.Dialect())
t1 := builder.Table(document.Table)
columns := dq.fields
columns := dq.ctx.Fields
if len(columns) == 0 {
columns = document.Columns
}
@@ -587,7 +579,7 @@ func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = dq.sql
selector.Select(selector.Columns(columns...)...)
}
if dq.unique != nil && *dq.unique {
if dq.ctx.Unique != nil && *dq.ctx.Unique {
selector.Distinct()
}
for _, p := range dq.predicates {
@@ -596,12 +588,12 @@ func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range dq.order {
p(selector)
}
if offset := dq.offset; offset != nil {
if offset := dq.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 := dq.limit; limit != nil {
if limit := dq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
@@ -621,7 +613,7 @@ func (dgb *DocumentGroupBy) Aggregate(fns ...AggregateFunc) *DocumentGroupBy {
// Scan applies the selector query and scans the result into the given value.
func (dgb *DocumentGroupBy) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeDocument, "GroupBy")
ctx = setContextOp(ctx, dgb.build.ctx, "GroupBy")
if err := dgb.build.prepareQuery(ctx); err != nil {
return err
}
@@ -669,7 +661,7 @@ func (ds *DocumentSelect) Aggregate(fns ...AggregateFunc) *DocumentSelect {
// Scan applies the selector query and scans the result into the given value.
func (ds *DocumentSelect) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeDocument, "Select")
ctx = setContextOp(ctx, ds.ctx, "Select")
if err := ds.prepareQuery(ctx); err != nil {
return err
}

View File

@@ -165,16 +165,7 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := du.check(); err != nil {
return n, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
Columns: document.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
if ps := du.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -199,10 +190,7 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -215,10 +203,7 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -234,10 +219,7 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -250,10 +232,7 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -269,10 +248,7 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -376,6 +352,12 @@ func (duo *DocumentUpdateOne) RemoveAttachments(a ...*Attachment) *DocumentUpdat
return duo.RemoveAttachmentIDs(ids...)
}
// Where appends a list predicates to the DocumentUpdate builder.
func (duo *DocumentUpdateOne) Where(ps ...predicate.Document) *DocumentUpdateOne {
duo.mutation.Where(ps...)
return duo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (duo *DocumentUpdateOne) Select(field string, fields ...string) *DocumentUpdateOne {
@@ -441,16 +423,7 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
if err := duo.check(); err != nil {
return _node, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
Columns: document.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
id, ok := duo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Document.id" for update`)}
@@ -492,10 +465,7 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -508,10 +478,7 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -527,10 +494,7 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -543,10 +507,7 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -562,10 +523,7 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -22,6 +22,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
@@ -31,6 +32,7 @@ type (
Hook = ent.Hook
Value = ent.Value
Query = ent.Query
QueryContext = ent.QueryContext
Querier = ent.Querier
QuerierFunc = ent.QuerierFunc
Interceptor = ent.Interceptor
@@ -43,6 +45,32 @@ type (
MutateFunc = ent.MutateFunc
)
type clientCtxKey struct{}
// FromContext returns a Client stored inside a context, or nil if there isn't one.
func FromContext(ctx context.Context) *Client {
c, _ := ctx.Value(clientCtxKey{}).(*Client)
return c
}
// NewContext returns a new context with the given Client attached.
func NewContext(parent context.Context, c *Client) context.Context {
return context.WithValue(parent, clientCtxKey{}, c)
}
type txCtxKey struct{}
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
func TxFromContext(ctx context.Context) *Tx {
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
return tx
}
// NewTxContext returns a new context with the given Tx attached.
func NewTxContext(parent context.Context, tx *Tx) context.Context {
return context.WithValue(parent, txCtxKey{}, tx)
}
// OrderFunc applies an ordering on the sql selector.
type OrderFunc func(*sql.Selector)
@@ -60,6 +88,7 @@ func columnChecker(table string) func(string) error {
label.Table: label.ValidColumn,
location.Table: location.ValidColumn,
maintenanceentry.Table: maintenanceentry.ValidColumn,
notifier.Table: notifier.ValidColumn,
user.Table: user.ValidColumn,
}
check, ok := checks[table]
@@ -500,7 +529,7 @@ func withHooks[V Value, M any, PM interface {
return exec(ctx)
}
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutationT, ok := m.(PM)
mutationT, ok := any(m).(PM)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
@@ -525,10 +554,11 @@ func withHooks[V Value, M any, PM interface {
return nv, nil
}
// newQueryContext returns a new context with the given QueryContext attached in case it does not exist.
func newQueryContext(ctx context.Context, typ, op string) context.Context {
// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist.
func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context {
if ent.QueryFromContext(ctx) == nil {
ctx = ent.NewQueryContext(ctx, &ent.QueryContext{Type: typ, Op: op})
qc.Op = op
ctx = ent.NewQueryContext(ctx, qc)
}
return ctx
}

View File

@@ -44,9 +44,11 @@ type GroupEdges struct {
Documents []*Document `json:"documents,omitempty"`
// InvitationTokens holds the value of the invitation_tokens edge.
InvitationTokens []*GroupInvitationToken `json:"invitation_tokens,omitempty"`
// Notifiers holds the value of the notifiers edge.
Notifiers []*Notifier `json:"notifiers,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
@@ -103,6 +105,15 @@ func (e GroupEdges) InvitationTokensOrErr() ([]*GroupInvitationToken, error) {
return nil, &NotLoadedError{edge: "invitation_tokens"}
}
// NotifiersOrErr returns the Notifiers value or an error if the edge
// was not loaded in eager-loading.
func (e GroupEdges) NotifiersOrErr() ([]*Notifier, error) {
if e.loadedTypes[6] {
return e.Notifiers, nil
}
return nil, &NotLoadedError{edge: "notifiers"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Group) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
@@ -166,39 +177,44 @@ func (gr *Group) assignValues(columns []string, values []any) error {
// QueryUsers queries the "users" edge of the Group entity.
func (gr *Group) QueryUsers() *UserQuery {
return (&GroupClient{config: gr.config}).QueryUsers(gr)
return NewGroupClient(gr.config).QueryUsers(gr)
}
// QueryLocations queries the "locations" edge of the Group entity.
func (gr *Group) QueryLocations() *LocationQuery {
return (&GroupClient{config: gr.config}).QueryLocations(gr)
return NewGroupClient(gr.config).QueryLocations(gr)
}
// QueryItems queries the "items" edge of the Group entity.
func (gr *Group) QueryItems() *ItemQuery {
return (&GroupClient{config: gr.config}).QueryItems(gr)
return NewGroupClient(gr.config).QueryItems(gr)
}
// QueryLabels queries the "labels" edge of the Group entity.
func (gr *Group) QueryLabels() *LabelQuery {
return (&GroupClient{config: gr.config}).QueryLabels(gr)
return NewGroupClient(gr.config).QueryLabels(gr)
}
// QueryDocuments queries the "documents" edge of the Group entity.
func (gr *Group) QueryDocuments() *DocumentQuery {
return (&GroupClient{config: gr.config}).QueryDocuments(gr)
return NewGroupClient(gr.config).QueryDocuments(gr)
}
// QueryInvitationTokens queries the "invitation_tokens" edge of the Group entity.
func (gr *Group) QueryInvitationTokens() *GroupInvitationTokenQuery {
return (&GroupClient{config: gr.config}).QueryInvitationTokens(gr)
return NewGroupClient(gr.config).QueryInvitationTokens(gr)
}
// QueryNotifiers queries the "notifiers" edge of the Group entity.
func (gr *Group) QueryNotifiers() *NotifierQuery {
return NewGroupClient(gr.config).QueryNotifiers(gr)
}
// 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.
func (gr *Group) Update() *GroupUpdateOne {
return (&GroupClient{config: gr.config}).UpdateOne(gr)
return NewGroupClient(gr.config).UpdateOne(gr)
}
// Unwrap unwraps the Group entity that was returned from a transaction after it was closed,
@@ -234,9 +250,3 @@ func (gr *Group) String() string {
// Groups is a parsable slice of Group.
type Groups []*Group
func (gr Groups) config(cfg config) {
for _i := range gr {
gr[_i].config = cfg
}
}

View File

@@ -34,6 +34,8 @@ const (
EdgeDocuments = "documents"
// EdgeInvitationTokens holds the string denoting the invitation_tokens edge name in mutations.
EdgeInvitationTokens = "invitation_tokens"
// EdgeNotifiers holds the string denoting the notifiers edge name in mutations.
EdgeNotifiers = "notifiers"
// Table holds the table name of the group in the database.
Table = "groups"
// UsersTable is the table that holds the users relation/edge.
@@ -78,6 +80,13 @@ const (
InvitationTokensInverseTable = "group_invitation_tokens"
// InvitationTokensColumn is the table column denoting the invitation_tokens relation/edge.
InvitationTokensColumn = "group_invitation_tokens"
// NotifiersTable is the table that holds the notifiers relation/edge.
NotifiersTable = "notifiers"
// NotifiersInverseTable is the table name for the Notifier entity.
// It exists in this package in order to avoid circular dependency with the "notifier" package.
NotifiersInverseTable = "notifiers"
// NotifiersColumn is the table column denoting the notifiers relation/edge.
NotifiersColumn = "group_id"
)
// Columns holds all SQL columns for group fields.
@@ -127,10 +136,17 @@ const (
CurrencyZar Currency = "zar"
CurrencyAud Currency = "aud"
CurrencyNok Currency = "nok"
CurrencyNzd Currency = "nzd"
CurrencySek Currency = "sek"
CurrencyDkk Currency = "dkk"
CurrencyInr Currency = "inr"
CurrencyRmb Currency = "rmb"
CurrencyBgn Currency = "bgn"
CurrencyChf Currency = "chf"
CurrencyPln Currency = "pln"
CurrencyTry Currency = "try"
CurrencyRon Currency = "ron"
CurrencyCzk Currency = "czk"
)
func (c Currency) String() string {
@@ -140,7 +156,7 @@ func (c Currency) String() string {
// CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save.
func CurrencyValidator(c Currency) error {
switch c {
case CurrencyUsd, CurrencyEur, CurrencyGbp, CurrencyJpy, CurrencyZar, CurrencyAud, CurrencyNok, CurrencySek, CurrencyDkk, CurrencyInr, CurrencyRmb:
case CurrencyUsd, CurrencyEur, CurrencyGbp, CurrencyJpy, CurrencyZar, CurrencyAud, CurrencyNok, CurrencyNzd, CurrencySek, CurrencyDkk, CurrencyInr, CurrencyRmb, CurrencyBgn, CurrencyChf, CurrencyPln, CurrencyTry, CurrencyRon, CurrencyCzk:
return nil
default:
return fmt.Errorf("group: invalid enum value for currency field: %q", c)

View File

@@ -398,6 +398,33 @@ func HasInvitationTokensWith(preds ...predicate.GroupInvitationToken) predicate.
})
}
// HasNotifiers applies the HasEdge predicate on the "notifiers" edge.
func HasNotifiers() predicate.Group {
return predicate.Group(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasNotifiersWith applies the HasEdge predicate on the "notifiers" edge with a given conditions (other predicates).
func HasNotifiersWith(preds ...predicate.Notifier) predicate.Group {
return predicate.Group(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(NotifiersInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn),
)
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(func(s *sql.Selector) {

View File

@@ -17,6 +17,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
@@ -179,6 +180,21 @@ func (gc *GroupCreate) AddInvitationTokens(g ...*GroupInvitationToken) *GroupCre
return gc.AddInvitationTokenIDs(ids...)
}
// AddNotifierIDs adds the "notifiers" edge to the Notifier entity by IDs.
func (gc *GroupCreate) AddNotifierIDs(ids ...uuid.UUID) *GroupCreate {
gc.mutation.AddNotifierIDs(ids...)
return gc
}
// AddNotifiers adds the "notifiers" edges to the Notifier entity.
func (gc *GroupCreate) AddNotifiers(n ...*Notifier) *GroupCreate {
ids := make([]uuid.UUID, len(n))
for i := range n {
ids[i] = n[i].ID
}
return gc.AddNotifierIDs(ids...)
}
// Mutation returns the GroupMutation object of the builder.
func (gc *GroupCreate) Mutation() *GroupMutation {
return gc.mutation
@@ -285,13 +301,7 @@ func (gc *GroupCreate) sqlSave(ctx context.Context) (*Group, error) {
func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
var (
_node = &Group{config: gc.config}
_spec = &sqlgraph.CreateSpec{
Table: group.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
}
_spec = sqlgraph.NewCreateSpec(group.Table, sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID))
)
if id, ok := gc.mutation.ID(); ok {
_node.ID = id
@@ -321,10 +331,7 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
Columns: []string{group.UsersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -340,10 +347,7 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
Columns: []string{group.LocationsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: location.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -359,10 +363,7 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
Columns: []string{group.ItemsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -378,10 +379,7 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
Columns: []string{group.LabelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: label.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -397,10 +395,7 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -416,10 +411,23 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
Columns: []string{group.InvitationTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := gc.mutation.NotifiersIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.NotifiersTable,
Columns: []string{group.NotifiersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(notifier.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -40,15 +40,7 @@ func (gd *GroupDelete) ExecX(ctx context.Context) int {
}
func (gd *GroupDelete) sqlExec(ctx context.Context) (int, error) {
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: group.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
},
}
_spec := sqlgraph.NewDeleteSpec(group.Table, sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID))
if ps := gd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -69,6 +61,12 @@ type GroupDeleteOne struct {
gd *GroupDelete
}
// Where appends a list predicates to the GroupDelete builder.
func (gdo *GroupDeleteOne) Where(ps ...predicate.Group) *GroupDeleteOne {
gdo.gd.mutation.Where(ps...)
return gdo
}
// Exec executes the deletion query.
func (gdo *GroupDeleteOne) Exec(ctx context.Context) error {
n, err := gdo.gd.Exec(ctx)
@@ -84,5 +82,7 @@ func (gdo *GroupDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (gdo *GroupDeleteOne) ExecX(ctx context.Context) {
gdo.gd.ExecX(ctx)
if err := gdo.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
@@ -25,11 +26,8 @@ import (
// GroupQuery is the builder for querying Group entities.
type GroupQuery struct {
config
limit *int
offset *int
unique *bool
ctx *QueryContext
order []OrderFunc
fields []string
inters []Interceptor
predicates []predicate.Group
withUsers *UserQuery
@@ -38,6 +36,7 @@ type GroupQuery struct {
withLabels *LabelQuery
withDocuments *DocumentQuery
withInvitationTokens *GroupInvitationTokenQuery
withNotifiers *NotifierQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -51,20 +50,20 @@ func (gq *GroupQuery) Where(ps ...predicate.Group) *GroupQuery {
// Limit the number of records to be returned by this query.
func (gq *GroupQuery) Limit(limit int) *GroupQuery {
gq.limit = &limit
gq.ctx.Limit = &limit
return gq
}
// Offset to start from.
func (gq *GroupQuery) Offset(offset int) *GroupQuery {
gq.offset = &offset
gq.ctx.Offset = &offset
return gq
}
// 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 (gq *GroupQuery) Unique(unique bool) *GroupQuery {
gq.unique = &unique
gq.ctx.Unique = &unique
return gq
}
@@ -206,10 +205,32 @@ func (gq *GroupQuery) QueryInvitationTokens() *GroupInvitationTokenQuery {
return query
}
// QueryNotifiers chains the current query on the "notifiers" edge.
func (gq *GroupQuery) QueryNotifiers() *NotifierQuery {
query := (&NotifierClient{config: gq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := gq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := gq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(group.Table, group.FieldID, selector),
sqlgraph.To(notifier.Table, notifier.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.NotifiersTable, group.NotifiersColumn),
)
fromU = sqlgraph.SetNeighbors(gq.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 (gq *GroupQuery) First(ctx context.Context) (*Group, error) {
nodes, err := gq.Limit(1).All(newQueryContext(ctx, TypeGroup, "First"))
nodes, err := gq.Limit(1).All(setContextOp(ctx, gq.ctx, "First"))
if err != nil {
return nil, err
}
@@ -232,7 +253,7 @@ func (gq *GroupQuery) FirstX(ctx context.Context) *Group {
// Returns a *NotFoundError when no Group ID was found.
func (gq *GroupQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = gq.Limit(1).IDs(newQueryContext(ctx, TypeGroup, "FirstID")); err != nil {
if ids, err = gq.Limit(1).IDs(setContextOp(ctx, gq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
@@ -255,7 +276,7 @@ func (gq *GroupQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one Group entity is found.
// Returns a *NotFoundError when no Group entities are found.
func (gq *GroupQuery) Only(ctx context.Context) (*Group, error) {
nodes, err := gq.Limit(2).All(newQueryContext(ctx, TypeGroup, "Only"))
nodes, err := gq.Limit(2).All(setContextOp(ctx, gq.ctx, "Only"))
if err != nil {
return nil, err
}
@@ -283,7 +304,7 @@ func (gq *GroupQuery) OnlyX(ctx context.Context) *Group {
// Returns a *NotFoundError when no entities are found.
func (gq *GroupQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = gq.Limit(2).IDs(newQueryContext(ctx, TypeGroup, "OnlyID")); err != nil {
if ids, err = gq.Limit(2).IDs(setContextOp(ctx, gq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
@@ -308,7 +329,7 @@ func (gq *GroupQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of Groups.
func (gq *GroupQuery) All(ctx context.Context) ([]*Group, error) {
ctx = newQueryContext(ctx, TypeGroup, "All")
ctx = setContextOp(ctx, gq.ctx, "All")
if err := gq.prepareQuery(ctx); err != nil {
return nil, err
}
@@ -326,10 +347,12 @@ func (gq *GroupQuery) AllX(ctx context.Context) []*Group {
}
// IDs executes the query and returns a list of Group IDs.
func (gq *GroupQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
ctx = newQueryContext(ctx, TypeGroup, "IDs")
if err := gq.Select(group.FieldID).Scan(ctx, &ids); err != nil {
func (gq *GroupQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if gq.ctx.Unique == nil && gq.path != nil {
gq.Unique(true)
}
ctx = setContextOp(ctx, gq.ctx, "IDs")
if err = gq.Select(group.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@@ -346,7 +369,7 @@ func (gq *GroupQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (gq *GroupQuery) Count(ctx context.Context) (int, error) {
ctx = newQueryContext(ctx, TypeGroup, "Count")
ctx = setContextOp(ctx, gq.ctx, "Count")
if err := gq.prepareQuery(ctx); err != nil {
return 0, err
}
@@ -364,7 +387,7 @@ func (gq *GroupQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (gq *GroupQuery) Exist(ctx context.Context) (bool, error) {
ctx = newQueryContext(ctx, TypeGroup, "Exist")
ctx = setContextOp(ctx, gq.ctx, "Exist")
switch _, err := gq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
@@ -392,8 +415,7 @@ func (gq *GroupQuery) Clone() *GroupQuery {
}
return &GroupQuery{
config: gq.config,
limit: gq.limit,
offset: gq.offset,
ctx: gq.ctx.Clone(),
order: append([]OrderFunc{}, gq.order...),
inters: append([]Interceptor{}, gq.inters...),
predicates: append([]predicate.Group{}, gq.predicates...),
@@ -403,10 +425,10 @@ func (gq *GroupQuery) Clone() *GroupQuery {
withLabels: gq.withLabels.Clone(),
withDocuments: gq.withDocuments.Clone(),
withInvitationTokens: gq.withInvitationTokens.Clone(),
withNotifiers: gq.withNotifiers.Clone(),
// clone intermediate query.
sql: gq.sql.Clone(),
path: gq.path,
unique: gq.unique,
sql: gq.sql.Clone(),
path: gq.path,
}
}
@@ -476,6 +498,17 @@ func (gq *GroupQuery) WithInvitationTokens(opts ...func(*GroupInvitationTokenQue
return gq
}
// WithNotifiers tells the query-builder to eager-load the nodes that are connected to
// the "notifiers" edge. The optional arguments are used to configure the query builder of the edge.
func (gq *GroupQuery) WithNotifiers(opts ...func(*NotifierQuery)) *GroupQuery {
query := (&NotifierClient{config: gq.config}).Query()
for _, opt := range opts {
opt(query)
}
gq.withNotifiers = query
return gq
}
// 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.
//
@@ -491,9 +524,9 @@ func (gq *GroupQuery) WithInvitationTokens(opts ...func(*GroupInvitationTokenQue
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (gq *GroupQuery) GroupBy(field string, fields ...string) *GroupGroupBy {
gq.fields = append([]string{field}, fields...)
gq.ctx.Fields = append([]string{field}, fields...)
grbuild := &GroupGroupBy{build: gq}
grbuild.flds = &gq.fields
grbuild.flds = &gq.ctx.Fields
grbuild.label = group.Label
grbuild.scan = grbuild.Scan
return grbuild
@@ -512,10 +545,10 @@ func (gq *GroupQuery) GroupBy(field string, fields ...string) *GroupGroupBy {
// Select(group.FieldCreatedAt).
// Scan(ctx, &v)
func (gq *GroupQuery) Select(fields ...string) *GroupSelect {
gq.fields = append(gq.fields, fields...)
gq.ctx.Fields = append(gq.ctx.Fields, fields...)
sbuild := &GroupSelect{GroupQuery: gq}
sbuild.label = group.Label
sbuild.flds, sbuild.scan = &gq.fields, sbuild.Scan
sbuild.flds, sbuild.scan = &gq.ctx.Fields, sbuild.Scan
return sbuild
}
@@ -535,7 +568,7 @@ func (gq *GroupQuery) prepareQuery(ctx context.Context) error {
}
}
}
for _, f := range gq.fields {
for _, f := range gq.ctx.Fields {
if !group.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@@ -554,13 +587,14 @@ func (gq *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
var (
nodes = []*Group{}
_spec = gq.querySpec()
loadedTypes = [6]bool{
loadedTypes = [7]bool{
gq.withUsers != nil,
gq.withLocations != nil,
gq.withItems != nil,
gq.withLabels != nil,
gq.withDocuments != nil,
gq.withInvitationTokens != nil,
gq.withNotifiers != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
@@ -625,6 +659,13 @@ func (gq *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
return nil, err
}
}
if query := gq.withNotifiers; query != nil {
if err := gq.loadNotifiers(ctx, query, nodes,
func(n *Group) { n.Edges.Notifiers = []*Notifier{} },
func(n *Group, e *Notifier) { n.Edges.Notifiers = append(n.Edges.Notifiers, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
@@ -814,33 +855,52 @@ func (gq *GroupQuery) loadInvitationTokens(ctx context.Context, query *GroupInvi
}
return nil
}
func (gq *GroupQuery) loadNotifiers(ctx context.Context, query *NotifierQuery, nodes []*Group, init func(*Group), assign func(*Group, *Notifier)) 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.Where(predicate.Notifier(func(s *sql.Selector) {
s.Where(sql.InValues(group.NotifiersColumn, fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.GroupID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected foreign-key "group_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (gq *GroupQuery) sqlCount(ctx context.Context) (int, error) {
_spec := gq.querySpec()
_spec.Node.Columns = gq.fields
if len(gq.fields) > 0 {
_spec.Unique = gq.unique != nil && *gq.unique
_spec.Node.Columns = gq.ctx.Fields
if len(gq.ctx.Fields) > 0 {
_spec.Unique = gq.ctx.Unique != nil && *gq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, gq.driver, _spec)
}
func (gq *GroupQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: group.Table,
Columns: group.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
},
From: gq.sql,
Unique: true,
}
if unique := gq.unique; unique != nil {
_spec := sqlgraph.NewQuerySpec(group.Table, group.Columns, sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID))
_spec.From = gq.sql
if unique := gq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if gq.path != nil {
_spec.Unique = true
}
if fields := gq.fields; len(fields) > 0 {
if fields := gq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, group.FieldID)
for i := range fields {
@@ -856,10 +916,10 @@ func (gq *GroupQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := gq.limit; limit != nil {
if limit := gq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := gq.offset; offset != nil {
if offset := gq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := gq.order; len(ps) > 0 {
@@ -875,7 +935,7 @@ func (gq *GroupQuery) querySpec() *sqlgraph.QuerySpec {
func (gq *GroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(gq.driver.Dialect())
t1 := builder.Table(group.Table)
columns := gq.fields
columns := gq.ctx.Fields
if len(columns) == 0 {
columns = group.Columns
}
@@ -884,7 +944,7 @@ func (gq *GroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = gq.sql
selector.Select(selector.Columns(columns...)...)
}
if gq.unique != nil && *gq.unique {
if gq.ctx.Unique != nil && *gq.ctx.Unique {
selector.Distinct()
}
for _, p := range gq.predicates {
@@ -893,12 +953,12 @@ func (gq *GroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range gq.order {
p(selector)
}
if offset := gq.offset; offset != nil {
if offset := gq.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 := gq.limit; limit != nil {
if limit := gq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
@@ -918,7 +978,7 @@ func (ggb *GroupGroupBy) Aggregate(fns ...AggregateFunc) *GroupGroupBy {
// Scan applies the selector query and scans the result into the given value.
func (ggb *GroupGroupBy) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeGroup, "GroupBy")
ctx = setContextOp(ctx, ggb.build.ctx, "GroupBy")
if err := ggb.build.prepareQuery(ctx); err != nil {
return err
}
@@ -966,7 +1026,7 @@ func (gs *GroupSelect) Aggregate(fns ...AggregateFunc) *GroupSelect {
// Scan applies the selector query and scans the result into the given value.
func (gs *GroupSelect) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeGroup, "Select")
ctx = setContextOp(ctx, gs.ctx, "Select")
if err := gs.prepareQuery(ctx); err != nil {
return err
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
@@ -151,6 +152,21 @@ func (gu *GroupUpdate) AddInvitationTokens(g ...*GroupInvitationToken) *GroupUpd
return gu.AddInvitationTokenIDs(ids...)
}
// AddNotifierIDs adds the "notifiers" edge to the Notifier entity by IDs.
func (gu *GroupUpdate) AddNotifierIDs(ids ...uuid.UUID) *GroupUpdate {
gu.mutation.AddNotifierIDs(ids...)
return gu
}
// AddNotifiers adds the "notifiers" edges to the Notifier entity.
func (gu *GroupUpdate) AddNotifiers(n ...*Notifier) *GroupUpdate {
ids := make([]uuid.UUID, len(n))
for i := range n {
ids[i] = n[i].ID
}
return gu.AddNotifierIDs(ids...)
}
// Mutation returns the GroupMutation object of the builder.
func (gu *GroupUpdate) Mutation() *GroupMutation {
return gu.mutation
@@ -282,6 +298,27 @@ func (gu *GroupUpdate) RemoveInvitationTokens(g ...*GroupInvitationToken) *Group
return gu.RemoveInvitationTokenIDs(ids...)
}
// ClearNotifiers clears all "notifiers" edges to the Notifier entity.
func (gu *GroupUpdate) ClearNotifiers() *GroupUpdate {
gu.mutation.ClearNotifiers()
return gu
}
// RemoveNotifierIDs removes the "notifiers" edge to Notifier entities by IDs.
func (gu *GroupUpdate) RemoveNotifierIDs(ids ...uuid.UUID) *GroupUpdate {
gu.mutation.RemoveNotifierIDs(ids...)
return gu
}
// RemoveNotifiers removes "notifiers" edges to Notifier entities.
func (gu *GroupUpdate) RemoveNotifiers(n ...*Notifier) *GroupUpdate {
ids := make([]uuid.UUID, len(n))
for i := range n {
ids[i] = n[i].ID
}
return gu.RemoveNotifierIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (gu *GroupUpdate) Save(ctx context.Context) (int, error) {
gu.defaults()
@@ -337,16 +374,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := gu.check(); err != nil {
return n, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: group.Table,
Columns: group.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(group.Table, group.Columns, sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID))
if ps := gu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -371,10 +399,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.UsersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -387,10 +412,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.UsersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -406,10 +428,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.UsersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -425,10 +444,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.LocationsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: location.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -441,10 +457,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.LocationsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: location.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -460,10 +473,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.LocationsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: location.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -479,10 +489,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.ItemsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -495,10 +502,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.ItemsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -514,10 +518,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.ItemsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -533,10 +534,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.LabelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: label.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -549,10 +547,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.LabelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: label.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -568,10 +563,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.LabelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: label.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -587,10 +579,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -603,10 +592,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -622,10 +608,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -641,10 +624,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.InvitationTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -657,10 +637,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.InvitationTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -676,10 +653,52 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{group.InvitationTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if gu.mutation.NotifiersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.NotifiersTable,
Columns: []string{group.NotifiersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(notifier.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := gu.mutation.RemovedNotifiersIDs(); len(nodes) > 0 && !gu.mutation.NotifiersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.NotifiersTable,
Columns: []string{group.NotifiersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(notifier.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 := gu.mutation.NotifiersIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.NotifiersTable,
Columns: []string{group.NotifiersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(notifier.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -823,6 +842,21 @@ func (guo *GroupUpdateOne) AddInvitationTokens(g ...*GroupInvitationToken) *Grou
return guo.AddInvitationTokenIDs(ids...)
}
// AddNotifierIDs adds the "notifiers" edge to the Notifier entity by IDs.
func (guo *GroupUpdateOne) AddNotifierIDs(ids ...uuid.UUID) *GroupUpdateOne {
guo.mutation.AddNotifierIDs(ids...)
return guo
}
// AddNotifiers adds the "notifiers" edges to the Notifier entity.
func (guo *GroupUpdateOne) AddNotifiers(n ...*Notifier) *GroupUpdateOne {
ids := make([]uuid.UUID, len(n))
for i := range n {
ids[i] = n[i].ID
}
return guo.AddNotifierIDs(ids...)
}
// Mutation returns the GroupMutation object of the builder.
func (guo *GroupUpdateOne) Mutation() *GroupMutation {
return guo.mutation
@@ -954,6 +988,33 @@ func (guo *GroupUpdateOne) RemoveInvitationTokens(g ...*GroupInvitationToken) *G
return guo.RemoveInvitationTokenIDs(ids...)
}
// ClearNotifiers clears all "notifiers" edges to the Notifier entity.
func (guo *GroupUpdateOne) ClearNotifiers() *GroupUpdateOne {
guo.mutation.ClearNotifiers()
return guo
}
// RemoveNotifierIDs removes the "notifiers" edge to Notifier entities by IDs.
func (guo *GroupUpdateOne) RemoveNotifierIDs(ids ...uuid.UUID) *GroupUpdateOne {
guo.mutation.RemoveNotifierIDs(ids...)
return guo
}
// RemoveNotifiers removes "notifiers" edges to Notifier entities.
func (guo *GroupUpdateOne) RemoveNotifiers(n ...*Notifier) *GroupUpdateOne {
ids := make([]uuid.UUID, len(n))
for i := range n {
ids[i] = n[i].ID
}
return guo.RemoveNotifierIDs(ids...)
}
// Where appends a list predicates to the GroupUpdate builder.
func (guo *GroupUpdateOne) Where(ps ...predicate.Group) *GroupUpdateOne {
guo.mutation.Where(ps...)
return guo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (guo *GroupUpdateOne) Select(field string, fields ...string) *GroupUpdateOne {
@@ -1016,16 +1077,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
if err := guo.check(); err != nil {
return _node, err
}
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: group.Table,
Columns: group.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(group.Table, group.Columns, sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID))
id, ok := guo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Group.id" for update`)}
@@ -1067,10 +1119,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.UsersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -1083,10 +1132,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.UsersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1102,10 +1148,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.UsersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1121,10 +1164,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.LocationsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: location.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -1137,10 +1177,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.LocationsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: location.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1156,10 +1193,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.LocationsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: location.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(location.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1175,10 +1209,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.ItemsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -1191,10 +1222,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.ItemsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1210,10 +1238,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.ItemsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1229,10 +1254,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.LabelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: label.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -1245,10 +1267,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.LabelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: label.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1264,10 +1283,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.LabelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: label.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(label.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1283,10 +1299,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -1299,10 +1312,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1318,10 +1328,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.DocumentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1337,10 +1344,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.InvitationTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -1353,10 +1357,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.InvitationTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -1372,10 +1373,52 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
Columns: []string{group.InvitationTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if guo.mutation.NotifiersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.NotifiersTable,
Columns: []string{group.NotifiersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(notifier.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := guo.mutation.RemovedNotifiersIDs(); len(nodes) > 0 && !guo.mutation.NotifiersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.NotifiersTable,
Columns: []string{group.NotifiersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(notifier.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 := guo.mutation.NotifiersIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.NotifiersTable,
Columns: []string{group.NotifiersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(notifier.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -136,14 +136,14 @@ func (git *GroupInvitationToken) assignValues(columns []string, values []any) er
// QueryGroup queries the "group" edge of the GroupInvitationToken entity.
func (git *GroupInvitationToken) QueryGroup() *GroupQuery {
return (&GroupInvitationTokenClient{config: git.config}).QueryGroup(git)
return NewGroupInvitationTokenClient(git.config).QueryGroup(git)
}
// Update returns a builder for updating this GroupInvitationToken.
// Note that you need to call GroupInvitationToken.Unwrap() before calling this method if this GroupInvitationToken
// was returned from a transaction, and the transaction was committed or rolled back.
func (git *GroupInvitationToken) Update() *GroupInvitationTokenUpdateOne {
return (&GroupInvitationTokenClient{config: git.config}).UpdateOne(git)
return NewGroupInvitationTokenClient(git.config).UpdateOne(git)
}
// Unwrap unwraps the GroupInvitationToken entity that was returned from a transaction after it was closed,
@@ -182,9 +182,3 @@ func (git *GroupInvitationToken) String() string {
// GroupInvitationTokens is a parsable slice of GroupInvitationToken.
type GroupInvitationTokens []*GroupInvitationToken
func (git GroupInvitationTokens) config(cfg config) {
for _i := range git {
git[_i].config = cfg
}
}

View File

@@ -220,13 +220,7 @@ func (gitc *GroupInvitationTokenCreate) sqlSave(ctx context.Context) (*GroupInvi
func (gitc *GroupInvitationTokenCreate) createSpec() (*GroupInvitationToken, *sqlgraph.CreateSpec) {
var (
_node = &GroupInvitationToken{config: gitc.config}
_spec = &sqlgraph.CreateSpec{
Table: groupinvitationtoken.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
}
_spec = sqlgraph.NewCreateSpec(groupinvitationtoken.Table, sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID))
)
if id, ok := gitc.mutation.ID(); ok {
_node.ID = id
@@ -260,10 +254,7 @@ func (gitc *GroupInvitationTokenCreate) createSpec() (*GroupInvitationToken, *sq
Columns: []string{groupinvitationtoken.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

View File

@@ -40,15 +40,7 @@ func (gitd *GroupInvitationTokenDelete) ExecX(ctx context.Context) int {
}
func (gitd *GroupInvitationTokenDelete) sqlExec(ctx context.Context) (int, error) {
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: groupinvitationtoken.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
},
}
_spec := sqlgraph.NewDeleteSpec(groupinvitationtoken.Table, sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID))
if ps := gitd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -69,6 +61,12 @@ type GroupInvitationTokenDeleteOne struct {
gitd *GroupInvitationTokenDelete
}
// Where appends a list predicates to the GroupInvitationTokenDelete builder.
func (gitdo *GroupInvitationTokenDeleteOne) Where(ps ...predicate.GroupInvitationToken) *GroupInvitationTokenDeleteOne {
gitdo.gitd.mutation.Where(ps...)
return gitdo
}
// Exec executes the deletion query.
func (gitdo *GroupInvitationTokenDeleteOne) Exec(ctx context.Context) error {
n, err := gitdo.gitd.Exec(ctx)
@@ -84,5 +82,7 @@ func (gitdo *GroupInvitationTokenDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (gitdo *GroupInvitationTokenDeleteOne) ExecX(ctx context.Context) {
gitdo.gitd.ExecX(ctx)
if err := gitdo.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -19,11 +19,8 @@ import (
// GroupInvitationTokenQuery is the builder for querying GroupInvitationToken entities.
type GroupInvitationTokenQuery struct {
config
limit *int
offset *int
unique *bool
ctx *QueryContext
order []OrderFunc
fields []string
inters []Interceptor
predicates []predicate.GroupInvitationToken
withGroup *GroupQuery
@@ -41,20 +38,20 @@ func (gitq *GroupInvitationTokenQuery) Where(ps ...predicate.GroupInvitationToke
// Limit the number of records to be returned by this query.
func (gitq *GroupInvitationTokenQuery) Limit(limit int) *GroupInvitationTokenQuery {
gitq.limit = &limit
gitq.ctx.Limit = &limit
return gitq
}
// Offset to start from.
func (gitq *GroupInvitationTokenQuery) Offset(offset int) *GroupInvitationTokenQuery {
gitq.offset = &offset
gitq.ctx.Offset = &offset
return gitq
}
// 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 (gitq *GroupInvitationTokenQuery) Unique(unique bool) *GroupInvitationTokenQuery {
gitq.unique = &unique
gitq.ctx.Unique = &unique
return gitq
}
@@ -89,7 +86,7 @@ func (gitq *GroupInvitationTokenQuery) QueryGroup() *GroupQuery {
// First returns the first GroupInvitationToken entity from the query.
// Returns a *NotFoundError when no GroupInvitationToken was found.
func (gitq *GroupInvitationTokenQuery) First(ctx context.Context) (*GroupInvitationToken, error) {
nodes, err := gitq.Limit(1).All(newQueryContext(ctx, TypeGroupInvitationToken, "First"))
nodes, err := gitq.Limit(1).All(setContextOp(ctx, gitq.ctx, "First"))
if err != nil {
return nil, err
}
@@ -112,7 +109,7 @@ func (gitq *GroupInvitationTokenQuery) FirstX(ctx context.Context) *GroupInvitat
// Returns a *NotFoundError when no GroupInvitationToken ID was found.
func (gitq *GroupInvitationTokenQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = gitq.Limit(1).IDs(newQueryContext(ctx, TypeGroupInvitationToken, "FirstID")); err != nil {
if ids, err = gitq.Limit(1).IDs(setContextOp(ctx, gitq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
@@ -135,7 +132,7 @@ func (gitq *GroupInvitationTokenQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one GroupInvitationToken entity is found.
// Returns a *NotFoundError when no GroupInvitationToken entities are found.
func (gitq *GroupInvitationTokenQuery) Only(ctx context.Context) (*GroupInvitationToken, error) {
nodes, err := gitq.Limit(2).All(newQueryContext(ctx, TypeGroupInvitationToken, "Only"))
nodes, err := gitq.Limit(2).All(setContextOp(ctx, gitq.ctx, "Only"))
if err != nil {
return nil, err
}
@@ -163,7 +160,7 @@ func (gitq *GroupInvitationTokenQuery) OnlyX(ctx context.Context) *GroupInvitati
// Returns a *NotFoundError when no entities are found.
func (gitq *GroupInvitationTokenQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = gitq.Limit(2).IDs(newQueryContext(ctx, TypeGroupInvitationToken, "OnlyID")); err != nil {
if ids, err = gitq.Limit(2).IDs(setContextOp(ctx, gitq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
@@ -188,7 +185,7 @@ func (gitq *GroupInvitationTokenQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of GroupInvitationTokens.
func (gitq *GroupInvitationTokenQuery) All(ctx context.Context) ([]*GroupInvitationToken, error) {
ctx = newQueryContext(ctx, TypeGroupInvitationToken, "All")
ctx = setContextOp(ctx, gitq.ctx, "All")
if err := gitq.prepareQuery(ctx); err != nil {
return nil, err
}
@@ -206,10 +203,12 @@ func (gitq *GroupInvitationTokenQuery) AllX(ctx context.Context) []*GroupInvitat
}
// IDs executes the query and returns a list of GroupInvitationToken IDs.
func (gitq *GroupInvitationTokenQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
ctx = newQueryContext(ctx, TypeGroupInvitationToken, "IDs")
if err := gitq.Select(groupinvitationtoken.FieldID).Scan(ctx, &ids); err != nil {
func (gitq *GroupInvitationTokenQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if gitq.ctx.Unique == nil && gitq.path != nil {
gitq.Unique(true)
}
ctx = setContextOp(ctx, gitq.ctx, "IDs")
if err = gitq.Select(groupinvitationtoken.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@@ -226,7 +225,7 @@ func (gitq *GroupInvitationTokenQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (gitq *GroupInvitationTokenQuery) Count(ctx context.Context) (int, error) {
ctx = newQueryContext(ctx, TypeGroupInvitationToken, "Count")
ctx = setContextOp(ctx, gitq.ctx, "Count")
if err := gitq.prepareQuery(ctx); err != nil {
return 0, err
}
@@ -244,7 +243,7 @@ func (gitq *GroupInvitationTokenQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (gitq *GroupInvitationTokenQuery) Exist(ctx context.Context) (bool, error) {
ctx = newQueryContext(ctx, TypeGroupInvitationToken, "Exist")
ctx = setContextOp(ctx, gitq.ctx, "Exist")
switch _, err := gitq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
@@ -272,16 +271,14 @@ func (gitq *GroupInvitationTokenQuery) Clone() *GroupInvitationTokenQuery {
}
return &GroupInvitationTokenQuery{
config: gitq.config,
limit: gitq.limit,
offset: gitq.offset,
ctx: gitq.ctx.Clone(),
order: append([]OrderFunc{}, gitq.order...),
inters: append([]Interceptor{}, gitq.inters...),
predicates: append([]predicate.GroupInvitationToken{}, gitq.predicates...),
withGroup: gitq.withGroup.Clone(),
// clone intermediate query.
sql: gitq.sql.Clone(),
path: gitq.path,
unique: gitq.unique,
sql: gitq.sql.Clone(),
path: gitq.path,
}
}
@@ -311,9 +308,9 @@ func (gitq *GroupInvitationTokenQuery) WithGroup(opts ...func(*GroupQuery)) *Gro
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (gitq *GroupInvitationTokenQuery) GroupBy(field string, fields ...string) *GroupInvitationTokenGroupBy {
gitq.fields = append([]string{field}, fields...)
gitq.ctx.Fields = append([]string{field}, fields...)
grbuild := &GroupInvitationTokenGroupBy{build: gitq}
grbuild.flds = &gitq.fields
grbuild.flds = &gitq.ctx.Fields
grbuild.label = groupinvitationtoken.Label
grbuild.scan = grbuild.Scan
return grbuild
@@ -332,10 +329,10 @@ func (gitq *GroupInvitationTokenQuery) GroupBy(field string, fields ...string) *
// Select(groupinvitationtoken.FieldCreatedAt).
// Scan(ctx, &v)
func (gitq *GroupInvitationTokenQuery) Select(fields ...string) *GroupInvitationTokenSelect {
gitq.fields = append(gitq.fields, fields...)
gitq.ctx.Fields = append(gitq.ctx.Fields, fields...)
sbuild := &GroupInvitationTokenSelect{GroupInvitationTokenQuery: gitq}
sbuild.label = groupinvitationtoken.Label
sbuild.flds, sbuild.scan = &gitq.fields, sbuild.Scan
sbuild.flds, sbuild.scan = &gitq.ctx.Fields, sbuild.Scan
return sbuild
}
@@ -355,7 +352,7 @@ func (gitq *GroupInvitationTokenQuery) prepareQuery(ctx context.Context) error {
}
}
}
for _, f := range gitq.fields {
for _, f := range gitq.ctx.Fields {
if !groupinvitationtoken.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@@ -425,6 +422,9 @@ func (gitq *GroupInvitationTokenQuery) loadGroup(ctx context.Context, query *Gro
}
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 {
@@ -444,30 +444,22 @@ func (gitq *GroupInvitationTokenQuery) loadGroup(ctx context.Context, query *Gro
func (gitq *GroupInvitationTokenQuery) sqlCount(ctx context.Context) (int, error) {
_spec := gitq.querySpec()
_spec.Node.Columns = gitq.fields
if len(gitq.fields) > 0 {
_spec.Unique = gitq.unique != nil && *gitq.unique
_spec.Node.Columns = gitq.ctx.Fields
if len(gitq.ctx.Fields) > 0 {
_spec.Unique = gitq.ctx.Unique != nil && *gitq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, gitq.driver, _spec)
}
func (gitq *GroupInvitationTokenQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: groupinvitationtoken.Table,
Columns: groupinvitationtoken.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
},
From: gitq.sql,
Unique: true,
}
if unique := gitq.unique; unique != nil {
_spec := sqlgraph.NewQuerySpec(groupinvitationtoken.Table, groupinvitationtoken.Columns, sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID))
_spec.From = gitq.sql
if unique := gitq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if gitq.path != nil {
_spec.Unique = true
}
if fields := gitq.fields; len(fields) > 0 {
if fields := gitq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, groupinvitationtoken.FieldID)
for i := range fields {
@@ -483,10 +475,10 @@ func (gitq *GroupInvitationTokenQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := gitq.limit; limit != nil {
if limit := gitq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := gitq.offset; offset != nil {
if offset := gitq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := gitq.order; len(ps) > 0 {
@@ -502,7 +494,7 @@ func (gitq *GroupInvitationTokenQuery) querySpec() *sqlgraph.QuerySpec {
func (gitq *GroupInvitationTokenQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(gitq.driver.Dialect())
t1 := builder.Table(groupinvitationtoken.Table)
columns := gitq.fields
columns := gitq.ctx.Fields
if len(columns) == 0 {
columns = groupinvitationtoken.Columns
}
@@ -511,7 +503,7 @@ func (gitq *GroupInvitationTokenQuery) sqlQuery(ctx context.Context) *sql.Select
selector = gitq.sql
selector.Select(selector.Columns(columns...)...)
}
if gitq.unique != nil && *gitq.unique {
if gitq.ctx.Unique != nil && *gitq.ctx.Unique {
selector.Distinct()
}
for _, p := range gitq.predicates {
@@ -520,12 +512,12 @@ func (gitq *GroupInvitationTokenQuery) sqlQuery(ctx context.Context) *sql.Select
for _, p := range gitq.order {
p(selector)
}
if offset := gitq.offset; offset != nil {
if offset := gitq.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 := gitq.limit; limit != nil {
if limit := gitq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
@@ -545,7 +537,7 @@ func (gitgb *GroupInvitationTokenGroupBy) Aggregate(fns ...AggregateFunc) *Group
// Scan applies the selector query and scans the result into the given value.
func (gitgb *GroupInvitationTokenGroupBy) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeGroupInvitationToken, "GroupBy")
ctx = setContextOp(ctx, gitgb.build.ctx, "GroupBy")
if err := gitgb.build.prepareQuery(ctx); err != nil {
return err
}
@@ -593,7 +585,7 @@ func (gits *GroupInvitationTokenSelect) Aggregate(fns ...AggregateFunc) *GroupIn
// Scan applies the selector query and scans the result into the given value.
func (gits *GroupInvitationTokenSelect) Scan(ctx context.Context, v any) error {
ctx = newQueryContext(ctx, TypeGroupInvitationToken, "Select")
ctx = setContextOp(ctx, gits.ctx, "Select")
if err := gits.prepareQuery(ctx); err != nil {
return err
}

View File

@@ -144,16 +144,7 @@ func (gitu *GroupInvitationTokenUpdate) defaults() {
}
func (gitu *GroupInvitationTokenUpdate) sqlSave(ctx context.Context) (n int, err error) {
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: groupinvitationtoken.Table,
Columns: groupinvitationtoken.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(groupinvitationtoken.Table, groupinvitationtoken.Columns, sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID))
if ps := gitu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@@ -184,10 +175,7 @@ func (gitu *GroupInvitationTokenUpdate) sqlSave(ctx context.Context) (n int, err
Columns: []string{groupinvitationtoken.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -200,10 +188,7 @@ func (gitu *GroupInvitationTokenUpdate) sqlSave(ctx context.Context) (n int, err
Columns: []string{groupinvitationtoken.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
@@ -308,6 +293,12 @@ func (gituo *GroupInvitationTokenUpdateOne) ClearGroup() *GroupInvitationTokenUp
return gituo
}
// Where appends a list predicates to the GroupInvitationTokenUpdate builder.
func (gituo *GroupInvitationTokenUpdateOne) Where(ps ...predicate.GroupInvitationToken) *GroupInvitationTokenUpdateOne {
gituo.mutation.Where(ps...)
return gituo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (gituo *GroupInvitationTokenUpdateOne) Select(field string, fields ...string) *GroupInvitationTokenUpdateOne {
@@ -352,16 +343,7 @@ func (gituo *GroupInvitationTokenUpdateOne) defaults() {
}
func (gituo *GroupInvitationTokenUpdateOne) sqlSave(ctx context.Context) (_node *GroupInvitationToken, err error) {
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: groupinvitationtoken.Table,
Columns: groupinvitationtoken.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: groupinvitationtoken.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(groupinvitationtoken.Table, groupinvitationtoken.Columns, sqlgraph.NewFieldSpec(groupinvitationtoken.FieldID, field.TypeUUID))
id, ok := gituo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "GroupInvitationToken.id" for update`)}
@@ -409,10 +391,7 @@ func (gituo *GroupInvitationTokenUpdateOne) sqlSave(ctx context.Context) (_node
Columns: []string{groupinvitationtoken.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@@ -425,10 +404,7 @@ func (gituo *GroupInvitationTokenUpdateOne) sqlSave(ctx context.Context) (_node
Columns: []string{groupinvitationtoken.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {

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