Compare commits

..

188 Commits

Author SHA1 Message Date
Hayden
64d2957853 implement password reset 2024-05-24 20:34:49 -05:00
Hayden
2231c54f21 fix erroro 2024-05-24 20:00:04 -05:00
Hayden
5c217c11d2 remove unused import 2024-05-24 19:58:48 -05:00
Hayden
75ad107192 i hate this 2024-05-24 19:55:24 -05:00
Hayden
4f51e4dc8c idk man 2024-05-24 19:53:59 -05:00
Hayden
0e6b893efb MAYBE THIS ONE 2024-05-24 19:46:34 -05:00
Hayden
d022795270 bump nuxt 2024-05-24 19:46:33 -05:00
Hayden
d87b68dd5c idk man 2024-05-24 19:46:33 -05:00
Hayden
80de7d83f9 please god work 2024-05-24 19:46:33 -05:00
Hayden
d322d98013 ignore file 2024-05-24 19:46:33 -05:00
Hayden
a35c522676 override no-unused-disable 2024-05-24 19:46:33 -05:00
Hayden
a75ab532c2 without --shamefully-hoist 2024-05-24 19:46:33 -05:00
Hayden
1f155c569f make sure dev installs 2024-05-24 19:46:32 -05:00
Hayden
d39e7ee97f modify frontend ci 2024-05-24 19:46:32 -05:00
Hayden
c7ada54e14 fix test cases 2024-05-24 19:46:32 -05:00
Hayden
41881ee8ec use tCtx 2024-05-24 19:46:32 -05:00
Hayden
d7fd68765a fix linter/test errors 2024-05-24 19:46:32 -05:00
Hayden
0c968de1fb reset password form UI 2024-05-24 19:46:32 -05:00
Hayden
c83d178182 request password reset form 2024-05-24 19:46:31 -05:00
Hayden
ef06e4fbb2 fix log to be warn 2024-05-24 19:46:31 -05:00
Hayden
f4c63613d5 auto return 204 and run request in background 2024-05-24 19:46:31 -05:00
Hayden
b96fb4bb9e fix lockfile 2024-05-24 19:46:31 -05:00
Hayden
cb949e6c86 ignore init for setting 2024-05-24 19:46:31 -05:00
Hayden
7ff78530a2 email styles 2024-05-24 19:46:31 -05:00
Hayden
29e30bfaba email improvements 2024-05-24 19:46:30 -05:00
CollapsingStar
6fd8457e5a chore: remove deprecated version tag for Docker v2 (#902) 2024-05-23 07:53:43 -08:00
Graeme B
c2511d56d7 Update README.md (#891)
Add a Contributing section and the Buy Me A Coffee link since I had to hunt for it.
2024-05-23 07:52:56 -08:00
renovate[bot]
fbb17d66aa fix(deps): update dependency @vueuse/nuxt to v10.9.0 (#827)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-28 08:11:54 -08:00
renovate[bot]
98429ae8c2 fix(deps): update dependency @vueuse/router to v10.9.0 (#828)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-28 08:11:46 -08:00
renovate[bot]
69adbacd25 fix(deps): update dependency h3 to v1.11.1 (#831)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-28 08:11:34 -08:00
renovate[bot]
43ff04317a fix(deps): update dependency pinia to v2.1.7 (#833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-28 08:11:22 -08:00
renovate[bot]
361c2121d8 fix(deps): update dependency daisyui to v2.52.0 (#830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-28 08:09:16 -08:00
Spreadcat
0041c277ad Add currency "norwegian krones" (#836)
* add: norwegian kroner

Adding the currency of norwegian krones back into the file. There was an earlier commit where this seems to have dropped of the table.

* fix: using the correct terms for the currency.
2024-03-12 14:48:51 -08:00
Hayden
f621d3ad5d fix blocking error during startup when in demo mode (#838) 2024-03-05 06:44:43 -09:00
Hayden
af9aa239af add email banner 2024-03-02 12:03:13 -06:00
renovate[bot]
f8a2c70f2c chore(deps): update typescript-eslint monorepo to v6.21.0 (#824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-02 08:14:23 -09:00
renovate[bot]
89b68da86e fix(deps): update dependency @vuepic/vue-datepicker to v8.2.0 (#826)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-02 08:14:03 -09:00
renovate[bot]
03e9596b7a fix(deps): update dependency @nuxtjs/tailwindcss to v6.11.4 (#825)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-02 08:06:01 -09:00
renovate[bot]
90535c8691 chore(deps): update dependency typescript to v5.3.3 (#822)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 18:32:52 -09:00
renovate[bot]
b7fc0e903c chore(deps): update dependency vitest to v1.3.1 (#823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 18:32:40 -09:00
renovate[bot]
eb420d50b1 chore(deps): update dependency eslint-config-prettier to v9.1.0 (#820)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 16:44:52 -09:00
renovate[bot]
8f3a081670 chore(deps): update dependency eslint-plugin-vue to v9.22.0 (#821)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 16:44:46 -09:00
Hayden
2867a05c92 chore: bump http kit (#817)
* use new httpkit runner

* refactor out last httpkit changes

* fix timeout defaults

* fix wrong time input - closes #819
2024-03-01 15:07:03 -09:00
renovate[bot]
77b4d594af fix(deps): update module ariga.io/atlas to v0.19.1 (#814)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 15:06:12 -09:00
renovate[bot]
8f4fed1df9 fix(deps): update dependency postcss to v8.4.35 (#812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 15:05:54 -09:00
renovate[bot]
b65da05d11 chore(deps): update dependency @nuxtjs/eslint-config-typescript to v12.1.0 (#816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 15:05:34 -09:00
renovate[bot]
c2c8441625 chore(deps): update dependency @faker-js/faker to v8.4.1 (#815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 15:05:24 -09:00
renovate[bot]
a1eb6e314c fix(deps): update dependency dompurify to v3.0.9 (#811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 10:52:03 -09:00
renovate[bot]
b0c9dcdeee fix(deps): update dependency autoprefixer to v10.4.18 (#809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 10:51:57 -09:00
renovate[bot]
0a609adb0b fix(deps): update dependency @tailwindcss/typography to v0.5.10 (#806)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 10:51:47 -09:00
renovate[bot]
19ee6c2687 fix(deps): update dependency @headlessui/vue to v1.7.19 (#803)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 10:51:40 -09:00
Hayden
b77c9be36f chore: bump deps (#810)
* bump prettier/eslint-plugin

* bump nuxt pwa

* use typed imports

* set vue version to fix layout errors

* disable import
2024-03-01 09:08:14 -09:00
Hayden
be2910e0df fix missing icon 2024-03-01 11:34:07 -06:00
Hayden
749ee367d4 upgrade dependency 2024-03-01 10:54:31 -06:00
renovate[bot]
c36ce7ac2d fix(deps): update module github.com/swaggo/http-swagger to v2 (#514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 07:51:26 -09:00
renovate[bot]
d0128c7bd7 fix(deps): update dependency @tailwindcss/forms to v0.5.7 (#804)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 16:20:51 -09:00
Hayden
f91b33db38 fix: remove external dependency for icones (#805)
* change all icons to use iconify

* fix minor UI elements

* fix layout of table
2024-02-29 16:20:18 -09:00
renovate[bot]
cf166ac641 chore(deps): update dependency prettier to v2.8.8 (#802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 12:17:27 -09:00
renovate[bot]
ae86e09970 chore(deps): update dependency @types/markdown-it to v13.0.7 (#801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 12:17:19 -09:00
renovate[bot]
a7e0d2d127 fix(deps): update dependency postcss to v8.4.31 [security] (#791)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 10:48:33 -09:00
renovate[bot]
9866fdddb8 fix(deps): update module modernc.org/sqlite to v1.29.2 (#790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 10:48:07 -09:00
renovate[bot]
ff9759ab20 chore(deps): update dependency mkdocs-material to v9.5.12 (#778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 10:47:58 -09:00
Hayden
b655cfab28 feat: improve search matching (#800)
* offload search to lunr.js

* update location search when locations are mutated
2024-02-29 10:45:05 -09:00
Hayden
4c9ddac395 fix: date picker improvements (#793)
* use vue component for date picker

* zero out database fields even when set to 0001-xx-xx

* fix wrong datetime display + improved datepicker

* fix ts error

* zero out times

* add date-fns to dependencies
2024-02-29 09:58:26 -09:00
Hayden
c708b1759e fix: profile bugs and add full path (#792)
* fix template rendering

* tidy

* fix missing currencies and profile errors

* endpoint for fullpath of an item

* endpoint test

* fix assertions
2024-02-25 13:04:24 -09:00
renovate[bot]
3ed50f5a1b fix(deps): update module modernc.org/sqlite to v1.29.1 (#754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-18 09:30:08 -09:00
renovate[bot]
e6d77b3f72 fix(deps): update module github.com/go-chi/chi/v5 to v5.0.12 (#775)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-18 09:29:53 -09:00
renovate[bot]
747815982e fix(deps): update module github.com/go-playground/validator/v10 to v10.18.0 (#753)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 10:43:58 -09:00
renovate[bot]
7e23a75908 fix(deps): update module github.com/hay-kot/httpkit to v0.0.6 (#751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 10:43:41 -09:00
renovate[bot]
4c8a97fe0d chore(deps): update dependency mkdocs-material to v9.5.9 (#750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 10:43:25 -09:00
renovate[bot]
87b07ab47d chore(deps): update golangci/golangci-lint-action action to v4 (#747)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 06:13:34 -09:00
renovate[bot]
cf8000d8ee chore(deps): update pnpm/action-setup action to v3 (#744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 06:11:03 -09:00
renovate[bot]
31b10194f1 fix(deps): update module golang.org/x/crypto to v0.19.0 (#743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 06:10:55 -09:00
renovate[bot]
3d748021dc chore(deps): update dependency mkdocs-material to v9.5.8 (#742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-07 09:45:51 -09:00
renovate[bot]
0f8e1fee35 fix(deps): update module github.com/rs/zerolog to v1.32.0 (#740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-07 09:45:44 -09:00
renovate[bot]
8d18b06012 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.22 (#735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 10:43:08 -09:00
renovate[bot]
6a32762c98 fix(deps): update module github.com/swaggo/swag to v1.16.3 (#731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 10:42:59 -09:00
renovate[bot]
2311eda44b fix(deps): update module ariga.io/atlas to v0.19.0 (#734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 10:42:50 -09:00
renovate[bot]
5b52aa8abf chore(deps): update dependency mkdocs-material to v9.5.7 (#736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-04 10:42:39 -09:00
LINKIWI
aace77ec40 backend: Periodically send client aliveness pings in event stream websocket connection (#729)
* backend: Periodically send client aliveness pings following event websocket connection

* backend: Single persistent global ping goroutine instead of per-session ticker
2024-01-31 10:20:56 -09:00
renovate[bot]
c55d421326 chore(deps): update dependency mkdocs-material to v9.5.6 (#728)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-31 10:20:31 -09:00
renovate[bot]
5ca8e0c35d fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.21 (#730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-31 10:16:31 -09:00
Hayden
aba853d598 redo atlas upgrade with huge binary 2024-01-28 10:50:43 -06:00
Hayden
2c8fd163ee chore: ignore common bin output 2024-01-27 22:03:46 -06:00
Hayden
d9497bd69e remove large bin + go tidy
Former-commit-id: 830b4d8e3efb66874f0310d6c49d37d57481f986
2024-01-27 21:48:32 -06:00
LINKIWI
893f3f6df6 chore: Enable gofmt in linter, fix all outstanding errors (#727)
Former-commit-id: 1d9477f510bb2d6ada4b720933f113f24c25913e
Former-commit-id: b22ff272ca37181c93c6e83db8f1e1e67a767ebd
2024-01-27 18:29:10 -09:00
LINKIWI
ca55e5ba94 fix: Case-insensitive attachment extension matching for item photo inference (#725)
Former-commit-id: 3a6865079e68e9c1ee9beda651cbf123c58fcfc6
2024-01-27 18:28:58 -09:00
LINKIWI
7753213657 fix: Use zerolog level parser, set global log level correctly from configuration (#724)
Former-commit-id: 08b46c2fe32bda46158eac46f82a2341bb1a93c2
2024-01-27 18:28:43 -09:00
renovate[bot]
b3f7b59243 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.20 (#723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 858cdec2d59198bb84cb4b713343e9bd503ebb6b
2024-01-26 06:10:22 -09:00
renovate[bot]
18a9b21a87 fix(deps): update module github.com/google/uuid to v1.6.0 (#719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 59a5a1782bd7c9c63684a300a808932fd0072a3f
2024-01-24 08:46:07 -09:00
renovate[bot]
853b473668 chore(deps): update dependency mkdocs-material to v9.5.5 (#720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: b8b706ee7447990546fd7d5cb9d257be9a3f23de
2024-01-24 08:42:19 -09:00
renovate[bot]
40905bc100 fix(deps): update module modernc.org/sqlite to v1.28.0 (#667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: de4081d0d29fe010d56baed60f0d3e08b13475cc
2024-01-20 07:32:34 -09:00
renovate[bot]
8be9df2b8d fix(deps): update module github.com/go-playground/validator/v10 to v10.17.0 (#717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 94399a93dc1d848fae46c596aa7ed0e719be09b0
2024-01-20 07:32:21 -09:00
renovate[bot]
bd9eb69313 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.19 (#668)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 6ee41038b16e87c68bc1f3ca749265c927a80b5a
2024-01-19 05:55:39 -09:00
renovate[bot]
fa31bb2448 chore(deps): update dependency mkdocs-material to v9.5.4 (#716)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: b3b81a3ccc025e67d60e1b6fa8aacad4bc84a51e
2024-01-19 05:52:33 -09:00
renovate[bot]
fcd33e59d9 fix(deps): update module golang.org/x/crypto to v0.18.0 (#706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: d374fae88f6c64760922bc8b62fe5be40389eb36
2024-01-18 10:46:51 -09:00
zebrapurring
84aee20208 feat: enable search by serial number or model number (#677)
Co-authored-by: zebrapurring <>
Former-commit-id: f2f780936066eb5bb3dbbdbe0d0f9ef319662e73
2024-01-18 10:46:40 -09:00
Hayden
2b79788fbe feat: user defined currencies (#700)
* basic currency service for loading at runtime

* api endpoint for currencies

* sort slice before return

* remove currency validation

* validate using currency service

* implement selecting dynamic currency options

* bump go version

* fix type definition

* specify explicit type

* change go versions

* proper types for assetId

* log/return currency error

* make case insensative

* use ToUpper instead

* feat: adding new currencies (#715)

* fix: task swag (#710)

Co-authored-by: Quoing <pavel.cadersky@mavenir.com>

* [feat] Adding new currencies

---------

Co-authored-by: quoing <quoing@users.noreply.github.com>
Co-authored-by: Quoing <pavel.cadersky@mavenir.com>
Co-authored-by: Bradley <41597815+userbradley@users.noreply.github.com>

* remove ts file and consoldate new values into json

* move flag to options namespace

* add env config for currencies

* basic documentaion

* remove in sync test

---------

Co-authored-by: quoing <quoing@users.noreply.github.com>
Co-authored-by: Quoing <pavel.cadersky@mavenir.com>
Co-authored-by: Bradley <41597815+userbradley@users.noreply.github.com>
Former-commit-id: c4b923847a1b695dcddd1b346adcccfd3f3ce706
2024-01-18 10:45:42 -09:00
quoing
ce923a5b4c fix: task swag (#710)
Co-authored-by: Quoing <pavel.cadersky@mavenir.com>
Former-commit-id: d2bde3f24112aaa033e813546631b194c569694d
2024-01-16 17:39:25 -09:00
renovate[bot]
e83ff89c9d chore(deps): update dependency mkdocs-material to v9.5.3 (#697)
Former-commit-id: d920e1e215426f8f432f0ff0fa55310b7ec17ef1
2024-01-04 15:49:56 -06:00
renovate[bot]
c71c4f3b16 fix(deps): update module github.com/go-chi/chi/v5 to v5.0.11 (#698)
Former-commit-id: 6d8260b4fe276c4766c8d99330cbf8707bd4f88e
2024-01-04 15:48:08 -06:00
renovate[bot]
33661587fd fix(deps): update module github.com/google/uuid to v1.5.0 (#664)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 8ede42f79bfadeff674dfc576225eaf2d3a943f4
2024-01-04 12:12:20 -09:00
Marcel K
27b72603e1 feat: show quantity in card view if quantity == 0 (hay-kot/homebox#672) (#673)
* feat: show quantity in card view if quantity == 0 (hay-kot/homebox#672)

* Update frontend/components/Item/Card.vue

---------

Co-authored-by: bee-eater <bee-eater@users.noreply.github.com>
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
Former-commit-id: d8e49622e52a45346485fc052932ee39b6518c63
2024-01-04 12:10:44 -09:00
renovate[bot]
a4d91adc1e fix(deps): update module golang.org/x/crypto to v0.17.0 [security] (#676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: f3f2116a2b1b0b975693002bf4ce1224fb373a8e
2024-01-04 12:09:24 -09:00
Hayden
03df23d97c fix: inaccruate 401 & sql busy error (#679)
* fix inaccruate 401 error on SQL db error

* init golangci-lint config

* linter autofix

* testify auto fixes

* fix sqlite busy errors

* fix naming

* more linter errors

* fix rest of linter issues

Former-commit-id: e8449b3a73
2024-01-04 08:55:26 -09:00
zebrapurring
5e83b28ff5 fix: Render newlines in markdown fields (#682)
Co-authored-by: zebrapurring <>
Former-commit-id: b9183109f2
2023-12-24 18:27:58 -09:00
Hayden
d759fad40c feat: add log statements + remove auto redirect (#671)
Former-commit-id: 3ee150e7d6
2023-12-15 18:32:44 -09:00
Hayden
1cc9bf459a fix: ensure rows are closed (#670)
Former-commit-id: 85c8e47016
2023-12-15 18:07:17 -09:00
Hayden
0c535aa8d8 feat: extract auth into provider (#663)
* extract auth into provider

* bump go version

* use pointer

* rebase

Former-commit-id: 8538877f52
2023-12-12 05:49:46 -09:00
renovate[bot]
3eb4c21263 fix(deps): update dependency markdown-it to v14 (#655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 522943687e
2023-12-12 05:44:48 -09:00
renovate[bot]
19c53dce2f chore(deps): update dependency vitest to v1 (#652)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: ff2577a739
2023-12-12 05:44:38 -09:00
renovate[bot]
3967bc86d0 chore(deps): update actions/setup-go action to v5 (#653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 0bc3969c0c
2023-12-12 05:44:23 -09:00
renovate[bot]
2b6e42b11f chore(deps): update dependency mkdocs-material to v9.5.2 (#654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 8a85b18997
2023-12-12 05:44:11 -09:00
renovate[bot]
d48de6b9f6 fix(deps): update github.com/gocarina/gocsv digest to b87c2d0 (#629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: b5592167a2
2023-12-01 09:39:52 -09:00
renovate[bot]
477a5fce97 fix(deps): update module golang.org/x/crypto to v0.16.0 (#645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 126c06a0fa
2023-12-01 09:39:44 -09:00
renovate[bot]
65efe7f575 fix(deps): update module github.com/hay-kot/httpkit to v0.0.5 (#643)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 014fdea4ff
2023-12-01 09:39:37 -09:00
Hayden
1b77b15db4 fix: ignore /api paths from service workers (#649)
Former-commit-id: f2363b85cb
2023-12-01 08:57:59 -09:00
Hayden
fc3bc3f010 fix: loading causing stale data to present in form(#650)
Former-commit-id: 4d9131ba7e
2023-12-01 08:57:43 -09:00
Hayden
81e76d9dd4 fix/feat: primary photo auto set and auto-set primary photo (#651)
* fix error when item doesn't have photo attachment

* automatically detect image types and set primary photo if first

* remove charts.js from libs

Former-commit-id: 321a83b634
2023-12-01 08:57:29 -09:00
renovate[bot]
bd1a241be1 chore(deps): update docker/setup-qemu-action action to v3 (#625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: d1d98bcb06
2023-11-30 14:25:55 -09:00
renovate[bot]
bc4c0c0b89 chore(deps): update goreleaser/goreleaser-action action to v5 (#626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: db00e7a268
2023-11-30 14:25:47 -09:00
renovate[bot]
c7936fc478 chore(deps): update dependency mkdocs-material to v9.4.14 (#630)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 7ec22ed9bd
2023-11-30 14:25:30 -09:00
Kevin Holtkamp
09ee4fef3a docs: Fix a bunch of grammar and spelling, rephrased some sentences to be more readable (#635)
Former-commit-id: 10c030a56b
2023-11-30 14:25:22 -09:00
Hayden
d6b0062ae9 feat: set version flag (#632)
Former-commit-id: 9edbda3daa
2023-11-24 10:02:02 -09:00
Hayden
df26a3eeb7 fix: allow zeroing out asset ids (#624)
Former-commit-id: 8cc0f30291
2023-11-15 18:46:47 -09:00
Hayden
798d873f76 fix: images in child items (#623)
* support parentID search

* fetch images for item children

Former-commit-id: afbc6a49ac
2023-11-15 18:41:24 -09:00
renovate[bot]
a26ea3bac5 chore(deps): update dependency @types/markdown-it to v13 (#577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 2594d4cdb4
2023-11-15 18:18:19 -09:00
renovate[bot]
812b464bc7 chore(deps): update dependency @vite-pwa/nuxt to ^0.2.0 (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 2eafa8e72f
2023-11-15 18:18:07 -09:00
renovate[bot]
e1f34c2507 fix(deps): update dependency @pinia/nuxt to ^0.5.0 (#620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: e65d44fa9e
2023-11-15 18:17:57 -09:00
Hayden
79be938531 feat: expose timeout variables (#622)
* expose timeout variables

* formatting

Former-commit-id: eeae790fe4
2023-11-15 18:17:43 -09:00
renovate[bot]
e3ddc68eb2 chore(deps): update actions/setup-node action to v4 (#621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: a70ee33759
2023-11-15 18:15:31 -09:00
Hayden
eb9175ab27 fix: use theme aware background color (#619)
Former-commit-id: 80e2071300
2023-11-15 18:01:03 -09:00
renovate[bot]
54b7b2c35f chore(deps): update pnpm/action-setup action to v2.4.0 (#511)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: db27d34b4f
2023-11-15 17:58:30 -09:00
Owen Valentine
ab98870350 feat : Validate bark, ntfy, generic+ shortcut (#591)
Former-commit-id: da22074ed3
2023-11-15 17:53:06 -09:00
Hayden
13c437c418 fix: ensure urls isn't encoded (#618)
Former-commit-id: 51ba15f84c
2023-11-15 17:51:19 -09:00
Hayden
ba2e66a014 fix: filepath sep on windows (#615)
Former-commit-id: b408318acb
2023-11-15 17:43:44 -09:00
Hayden
1adf24e109 chore: bump all go dependencies (#614)
* bump all

* code-generation

Former-commit-id: c0e8e34065
2023-11-15 17:30:49 -09:00
Hayden
742ece7923 chore: rewrite generator to resolve strange name generation (#612)
* rewrite generator to resolve strange name generation

* fix asset id types

* ignore errored types

* remove depreciated calls

* use more random words

* random user

Former-commit-id: 4738a9b131
2023-11-15 17:19:51 -09:00
renovate[bot]
f74736b369 chore(deps): update actions/checkout action to v4 (#575)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: b08e52104c
2023-11-15 08:08:46 -09:00
renovate[bot]
3a3be89b90 chore(deps): update docker/setup-buildx-action action to v3 (#579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 3e2ab29054
2023-11-15 08:08:37 -09:00
renovate[bot]
4082092560 chore(deps): update dependency mkdocs-material to v9.4.8 (#592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: e5f66d99bc
2023-11-15 08:08:26 -09:00
Hayden
31839eb444 fix: restore location section (#587)
Former-commit-id: 0995478cc0
2023-10-15 06:37:47 -08:00
Hayden
6e203e7833 fix: infinite redirect issue (#583)
Former-commit-id: ae4b95301f
2023-10-10 05:43:44 -08:00
Hayden
0ef9d0deb8 Revert "chore(deps): update dependency nuxt to v3.7.4 (#554)" (#580)
This reverts commit 36e13ab03b [formerly 2cd3c15215].

Former-commit-id: d8482f3a13
2023-10-06 20:29:27 -08:00
Hayden
c71f077466 refactor: rewrite to cookie based auth (#578)
* rewrite to cookie based auth

* remove interceptor

Former-commit-id: 1365bdfd46
2023-10-06 19:44:43 -08:00
renovate[bot]
36e13ab03b chore(deps): update dependency nuxt to v3.7.4 (#554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 2cd3c15215
2023-10-06 18:51:54 -08:00
renovate[bot]
c0f19cdf05 fix(deps): update module modernc.org/sqlite to v1.26.0 (#574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 0dc4fa5d98
2023-10-06 18:51:44 -08:00
Hayden
b20c88e256 feat: primary images (#576)
* add support for primary images

* fix locked loading state issue

* add action to auto-set images

Former-commit-id: 318b8be192
2023-10-06 18:51:08 -08:00
Hayden
ce16b37b97 fix: field values request fails (#573)
Former-commit-id: 63a966c526
2023-10-06 14:10:44 -08:00
Hayden
6ed1893bed feat: make selectables clearable (#572)
Former-commit-id: db16d3fb23
2023-10-06 13:32:49 -08:00
renovate[bot]
bd9ea571aa fix(deps): update module golang.org/x/crypto to v0.14.0 (#570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 1952b9f1cb
2023-10-06 13:24:12 -08:00
renovate[bot]
10d9c4fbcc fix(deps): update module github.com/rs/zerolog to v1.31.0 (#569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 2b31d46ab3
2023-10-06 13:24:00 -08:00
Hayden
eff5db3664 fix: ensure loading in toggled (#571)
Former-commit-id: f3f96723b2
2023-10-06 13:22:16 -08:00
renovate[bot]
f8a6160039 fix(deps): update module github.com/yeqown/go-qrcode/writer/standard to v1.2.2 (#567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: b28bb2c4a8
2023-10-06 13:06:00 -08:00
renovate[bot]
4375ff8bd6 fix(deps): update module github.com/containrrr/shoutrrr to v0.8.0 (#555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: a33cf54a33
2023-10-06 13:05:53 -08:00
Hayden
9bee6e9863 pr: fixed incorrect sum of the total items price (#568)
* Fixed incorrect sum of the total items price

https://github.com/hay-kot/homebox/issues/458

* fix eslint errors

---------

Co-authored-by: Adamko <33964772+cRaZy92@users.noreply.github.com>
Former-commit-id: f13bf2958d
2023-10-06 12:50:55 -08:00
renovate[bot]
b42d20b6a6 chore(deps): update dependency mkdocs-material to v9.4.4 (#553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: a9712c48af
2023-10-06 12:31:51 -08:00
renovate[bot]
ba1e3a905b fix(deps): update module github.com/go-playground/validator/v10 to v10.15.5 (#556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: e68b7cf500
2023-10-06 12:30:31 -08:00
renovate[bot]
973d5ca97d fix(deps): update module github.com/swaggo/swag to v1.16.2 (#552)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 744a5bbb47
2023-09-14 06:19:57 -08:00
renovate[bot]
57baf22534 fix(deps): update module github.com/google/uuid to v1.3.1 (#551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: fc5698410b
2023-09-14 06:19:37 -08:00
renovate[bot]
c615707cbc fix(deps): update module golang.org/x/crypto to v0.13.0 (#532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 455163d637
2023-09-14 06:19:28 -08:00
Aaron von Awesome
992eea3278 fix: minor typo (#546)
Former-commit-id: fbc7e6e33a
2023-09-14 06:13:42 -08:00
renovate[bot]
03288b52ee chore(deps): update dependency eslint-config-prettier to v9 (#533)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 5739b2005a
2023-09-14 06:11:08 -08:00
renovate[bot]
a274bbcf64 fix(deps): update module modernc.org/sqlite to v1.25.0 (#531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 5f41960c0a
2023-09-14 06:10:46 -08:00
renovate[bot]
64e9889d41 fix(deps): update module github.com/rs/zerolog to v1.30.0 (#517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: c89aa738cf
2023-09-14 06:10:37 -08:00
Jonathan Gawrych
58e80ab3e0 fix: mobile "Create and Add Another" goes off screen (#540)
Former-commit-id: 94fd9c314d
2023-08-31 09:07:45 -08:00
Hayden
94e81d14fa fix websocket over secure connection (#542)
* fix https connection

* explicity dependency

Former-commit-id: 0876deb1e9
2023-08-24 06:28:56 -08:00
tctlrd
0b021e898f feat: add currencies XAG and XAU (#535)
* Added currencies XAG and XAU to currency.ts

I added XAG and XAU for myself and others who prefer to measure value with something of substance.

Review the ISO 4217 standard to view a full list of official currency codes including the ones I have added.

https://www.iso.org/iso-4217-currency-codes.html
https://en.wikipedia.org/wiki/ISO_4217

Example:
https://www.xe.com/currencyconverter/convert/?Amount=100&From=XAG&To=USD

API for exchange rates:
https://openexchangerates.org/

* Added field values xag and xau to group.go

* Update group.go
Former-commit-id: 5438898b49
2023-08-23 09:29:22 -08:00
Hayden
be8d6e8235 update lock file
Former-commit-id: 9fa17bec90
2023-08-09 21:49:32 -05:00
Cheng Gu
d3dcb599ca feat: set cookies' expires attribute and fix remember me (#530)
Former-commit-id: b5987f2e8d
2023-08-09 18:48:39 -08:00
Hayden
0cbe516ca3 feat: WebSocket based implementation of server sent events for cache busting (#527)
* rough implementation of WS based event system for server side notifications of mutation

* fix test construction

* fix deadlock on event bus

* disable linter error

* add item mutation events

* remove old event bus code

* refactor event system to use composables

* refresh items table when new item is added

* fix create form errors

* cleanup unnecessary calls

* fix importer erorrs + limit fn calls on import

Former-commit-id: 2cbcc8bb1d
2023-08-02 13:00:57 -08:00
Hayden
26911e9530 specify h3 dependency
Former-commit-id: cceec06148
2023-08-02 09:05:07 -05:00
Hayden
5524dfa83d try node 18
Former-commit-id: 2e2eed143d
2023-08-02 09:01:47 -05:00
renovate[bot]
21d54eb2ac chore(deps): update dependency vitest to ^0.34.0 (#529)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 272cc5a370
2023-08-02 05:59:31 -08:00
Hayden
4152199f3b build nightly rootless
Former-commit-id: 275e106d72
2023-08-02 08:47:53 -05:00
Hayden
1d40251f29 include rootless dockerfile
Former-commit-id: 3f0e65a2ad
2023-08-02 08:45:22 -05:00
Hayden
97e137c411 feat: add support for create + add more for all create modals and support k… (#526)
* add support for create + add more for all create modals and support keyboard bindings

* listen for esc to close modals

Former-commit-id: 22bbaae08f
2023-07-31 09:53:26 -08:00
Hayden
0de6c2338d fix: prevent resetting dialog state on error (#524)
Former-commit-id: 8c7d91ea52
2023-07-31 08:22:08 -08:00
Hayden
a2479155b2 feat: support cmd+s / ctrl+s and rework button display on edit (#523)
Former-commit-id: 5a219f6a9c
2023-07-31 06:57:42 -08:00
Hayden
e2dace75f4 fix: label prop not being passed to password input (#522)
Former-commit-id: 895017b28e
2023-07-31 06:08:35 -08:00
Hayden
ef9a7cd811 fix: assert/asserts (#521)
Former-commit-id: 02ce52dbe3
2023-07-31 06:05:37 -08:00
Hayden
964270e054 feat: more currency support (#520)
* add multiple new currencies

* add multiple new currencies

* remove duplicate yen

Former-commit-id: c5ae6b17f9
2023-07-31 05:59:36 -08:00
renovate[bot]
6b9cdf3294 chore(deps): update dependency mkdocs-material to v9.1.21 (#512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 371fc0a6af
2023-07-29 09:54:55 -08:00
Hayden
6fd228f1f4 ui: rework location/labels pages (#475)
* formatting

* slimdown locations page

* update location/labels

* fix dependency issues

* fix type generator

* cleanup unused variables

Former-commit-id: 016780920d
2023-07-27 13:21:28 -08:00
db8200
0eb0b283b2 fix 3 places where API URLs were not constructed by function route (#451)
* Fixed 3 places where API URLs were not constructed by function route(path, params).

* autofix

---------

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
Former-commit-id: 06eb6c1f91
2023-07-22 20:11:29 -08:00
renovate[bot]
4b071bccda fix(deps): update module github.com/swaggo/http-swagger to v2 (#508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: 27dad0e118
2023-07-22 20:10:52 -08:00
renovate[bot]
65a7947932 fix(deps): update module github.com/swaggo/http-swagger to v2 (#506)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Former-commit-id: dc9446516a
2023-07-22 20:09:43 -08:00
Hayden
a3e607a887 chore: bump all go deps (#507)
* bump all deps

* run code-gen

Former-commit-id: a042496c71
2023-07-22 19:57:51 -08:00
230 changed files with 23706 additions and 8611 deletions

View File

@@ -35,6 +35,6 @@
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node",
"features": {
"golang": "1.20"
"golang": "1.21"
}
}

View File

@@ -10,9 +10,9 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "1.21"
- name: Install Task
uses: arduino/setup-task@v1
@@ -20,7 +20,7 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest

View File

@@ -13,9 +13,13 @@ jobs:
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2.4.0
- uses: actions/setup-node@v4
with:
version: 6.0.2
node-version: 20
- uses: pnpm/action-setup@v3.0.0
with:
version: 9
- name: Install dependencies
run: pnpm install --shamefully-hoist
@@ -44,20 +48,20 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "1.22"
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v2.4.0
- uses: pnpm/action-setup@v3.0.0
with:
version: 6.0.2
version: 9
- name: Install dependencies
run: pnpm install
run: pnpm install --shamefully-hoist
working-directory: frontend
- name: Run Integration Tests

View File

@@ -22,7 +22,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.20"

View File

@@ -27,7 +27,7 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
- uses: pnpm/action-setup@v2
with:

3
.gitignore vendored
View File

@@ -53,4 +53,5 @@ dist/
# Nuxt Publish Dir
backend/app/api/static/public/*
!backend/app/api/static/public/.gitkeep
!backend/app/api/static/public/.gitkeep
backend/api

View File

@@ -16,7 +16,7 @@
"editor.formatOnSave": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"

View File

@@ -32,7 +32,7 @@ FROM alpine:latest
ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1
RUN apk --no-cache add ca-certificates
RUN mkdir /app

View File

@@ -30,6 +30,14 @@ docker run -d \
# ghcr.io/hay-kot/homebox:latest-rootless
```
<!-- CONTRIBUTING -->
## Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
If you are not a coder, you can still contribute financially. Financial contributions help me prioritize working on this project over others and helps me know that there is a real demand for project development.
<a href="https://www.buymeacoffee.com/haykot" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 107px !important;" ></a>
## Credits
- Logo by [@lakotelman](https://github.com/lakotelman)

View File

@@ -1,9 +1,15 @@
version: "3"
env:
HBOX_LOG_LEVEL: debug
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"
HBOX_MAILER_HOST: 127.0.0.1
HBOX_MAILER_PORT: 1025
HBOX_MAILER_USERNAME: c836555d57d205
HBOX_MAILER_PASSWORD: 3ff2f9986f3cff
HBOX_MAILER_FROM: info@example.com
tasks:
setup:
desc: Install development dependencies
@@ -78,6 +84,12 @@ tasks:
cmds:
- go mod tidy
go:fmt:
desc: Runs go fmt on the backend
dir: backend
cmds:
- gofumpt -w .
go:lint:
desc: Runs golangci-lint
dir: backend
@@ -102,8 +114,7 @@ tasks:
dir: backend/internal/
cmds:
- |
go generate ./... \
--template=./data/ent/schema/templates/has_id.tmpl
go generate ./...
sources:
- "./backend/internal/data/ent/schema/**/*"

77
backend/.golangci.yml Normal file
View File

@@ -0,0 +1,77 @@
run:
timeout: 10m
linters-settings:
errcheck:
exclude-functions:
- (net/http.ResponseWriter).Write
goconst:
min-len: 5
min-occurrences: 5
exhaustive:
default-signifies-exhaustive: true
revive:
ignore-generated-header: false
severity: warning
confidence: 3
depguard:
rules:
main:
deny:
- pkg: io/util
desc: |
Deprecated: As of Go 1.16, the same functionality is now provided by
package io or package os, and those implementations should be
preferred in new code. See the specific function documentation for
details.
gocritic:
enabled-checks:
- ruleguard
testifylint:
enable-all: true
tagalign:
order:
- json
- schema
- yaml
- yml
- toml
- validate
linters:
disable-all: true
enable:
- asciicheck
- bodyclose
- depguard
- dogsled
- errcheck
- errorlint
- exhaustive
- exportloopref
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goprintffuncname
- gosimple
- govet
- ineffassign
- misspell
- nakedret
- revive
- staticcheck
- stylecheck
- tagalign
- testifylint
- typecheck
- typecheck
- unconvert
- unused
- whitespace
- zerologlint
- sqlclosecheck
issues:
exclude-use-default: false
fix: false
exclude-dirs:
- internal/data/ent.*

Binary file not shown.

View File

@@ -1,22 +1,18 @@
package main
import (
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"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/config"
"github.com/hay-kot/homebox/backend/pkgs/mailer"
"github.com/hay-kot/httpkit/server"
)
type app struct {
conf *config.Config
mailer mailer.Mailer
mailer *mailer.Mailer
db *ent.Client
server *server.Server
repos *repo.AllRepos
services *services.AllServices
bus *eventbus.EventBus
@@ -27,7 +23,7 @@ func new(conf *config.Config) *app {
conf: conf,
}
s.mailer = mailer.Mailer{
s.mailer = &mailer.Mailer{
Host: s.conf.Mailer.Host,
Port: s.conf.Mailer.Port,
Username: s.conf.Mailer.Username,
@@ -37,13 +33,3 @@ func new(conf *config.Config) *app {
return s
}
func (a *app) startBgTask(t time.Duration, fn func()) {
timer := time.NewTimer(t)
for {
timer.Reset(t)
a.server.Background(fn)
<-timer.C
}
}

View File

@@ -0,0 +1,37 @@
package main
import (
"context"
"time"
)
type BackgroundTask struct {
name string
Interval time.Duration
Fn func(context.Context)
}
func (tsk *BackgroundTask) Name() string {
return tsk.name
}
func NewTask(name string, interval time.Duration, fn func(context.Context)) *BackgroundTask {
return &BackgroundTask{
Interval: interval,
Fn: fn,
}
}
func (tsk *BackgroundTask) Start(ctx context.Context) error {
timer := time.NewTimer(tsk.Interval)
for {
select {
case <-ctx.Done():
return nil
case <-timer.C:
timer.Reset(tsk.Interval)
tsk.Fn(ctx)
}
}
}

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/rs/zerolog/log"
@@ -15,9 +16,12 @@ func (a *app) SetupDemo() {
,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,,,,,,,
,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,,
,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,,
,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,,,,,,,
,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,,,,,,,
`
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
registration := services.UserRegistration{
Email: "demo@example.com",
Name: "Demo",
@@ -25,21 +29,32 @@ func (a *app) SetupDemo() {
}
// 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, false)
log.Debug().Msg("Checking if demo user already exists")
_, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err == nil {
log.Info().Msg("Demo user already exists, skipping setup")
return
}
_, err = a.services.User.RegisterUser(context.Background(), registration)
log.Debug().Msg("Demo user does not exist, setting up demo")
_, err = a.services.User.RegisterUser(ctx, registration)
if err != nil {
log.Err(err).Msg("Failed to register demo user")
log.Fatal().Msg("Failed to setup demo") // nolint
}
token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err != nil {
log.Err(err).Msg("Failed to login demo user")
log.Fatal().Msg("Failed to setup demo")
}
self, err := a.services.User.GetSelf(ctx, token.Raw)
if err != nil {
log.Err(err).Msg("Failed to get self")
log.Fatal().Msg("Failed to setup demo")
}
token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password, false)
self, _ := a.services.User.GetSelf(context.Background(), token.Raw)
_, err = a.services.Items.CsvImport(context.Background(), self.GroupID, strings.NewReader(csvText))
_, err = a.services.Items.CsvImport(ctx, 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

@@ -1,3 +1,4 @@
// Package debughandlers provides handlers for debugging.
package debughandlers
import (

View File

@@ -1,8 +1,10 @@
// Package v1 provides the API handlers for version 1 of the API.
package v1
import (
"fmt"
"encoding/json"
"net/http"
"time"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
@@ -74,7 +76,7 @@ type (
BuildTime string `json:"buildTime"`
}
ApiSummary struct {
APISummary struct {
Healthy bool `json:"health"`
Versions []string `json:"versions"`
Title string `json:"title"`
@@ -85,7 +87,7 @@ type (
}
)
func BaseUrlFunc(prefix string) func(s string) string {
func BaseURLFunc(prefix string) func(s string) string {
return func(s string) string {
return prefix + "/v1" + s
}
@@ -111,11 +113,11 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *event
// @Summary Application Info
// @Tags Base
// @Produce json
// @Success 200 {object} ApiSummary
// @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.JSON(w, http.StatusOK, ApiSummary{
return server.JSON(w, http.StatusOK, APISummary{
Healthy: ready(),
Title: "Homebox",
Message: "Track, Manage, and Organize your Things",
@@ -126,12 +128,32 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand
}
}
// HandleCurrency godoc
//
// @Summary Currency
// @Tags Base
// @Produce json
// @Success 200 {object} currencies.Currency
// @Router /v1/currency [GET]
func (ctrl *V1Controller) HandleCurrency() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// Set Cache for 10 Minutes
w.Header().Set("Cache-Control", "max-age=600")
return server.JSON(w, http.StatusOK, ctrl.svc.Currencies.Slice())
}
}
func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc {
type eventMsg struct {
Event string `json:"event"`
}
m := melody.New()
m.HandleConnect(func(s *melody.Session) {
auth := services.NewContext(s.Request.Context())
s.Set("gid", auth.GID)
s.Set("gid", auth.GroupID)
})
factory := func(e string) func(data any) {
@@ -142,9 +164,15 @@ func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc {
return
}
jsonStr := fmt.Sprintf(`{"event": "%s"}`, e)
msg := &eventMsg{Event: e}
_ = m.BroadcastFilter([]byte(jsonStr), func(s *melody.Session) bool {
jsonBytes, err := json.Marshal(msg)
if err != nil {
log.Log().Msgf("error marshling event data %v: %v", data, err)
return
}
_ = m.BroadcastFilter(jsonBytes, func(s *melody.Session) bool {
groupIDStr, ok := s.Get("gid")
if !ok {
return false
@@ -160,6 +188,25 @@ func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc {
ctrl.bus.Subscribe(eventbus.EventLocationMutation, factory("location.mutation"))
ctrl.bus.Subscribe(eventbus.EventItemMutation, factory("item.mutation"))
// Persistent asynchronous ticker that keeps all websocket connections alive with periodic pings.
go func() {
const interval = 10 * time.Second
ping := time.NewTicker(interval)
defer ping.Stop()
for range ping.C {
msg := &eventMsg{Event: "ping"}
pingBytes, err := json.Marshal(msg)
if err != nil {
log.Log().Msgf("error marshaling ping: %v", err)
} else {
_ = m.Broadcast(pingBytes)
}
}
}()
return func(w http.ResponseWriter, r *http.Request) error {
return m.HandleRequest(w, r)
}

View File

@@ -20,7 +20,7 @@ func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int,
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
totalCompleted, err := fn(ctx, ctx.GID)
totalCompleted, err := fn(ctx, ctx.GroupID)
if err != nil {
log.Err(err).Str("action_ref", ref).Msg("failed to run action")
return validate.NewRequestError(err, http.StatusInternalServerError)

View File

@@ -27,10 +27,10 @@ import (
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")
assetIdParam = strings.ReplaceAll(assetIdParam, "-", "") // Remove dashes
assetIDParam := chi.URLParam(r, "id")
assetIDParam = strings.ReplaceAll(assetIDParam, "-", "") // Remove dashes
// Convert the asset ID to an int64
assetId, err := strconv.ParseInt(assetIdParam, 10, 64)
assetID, err := strconv.ParseInt(assetIDParam, 10, 64)
if err != nil {
return err
}
@@ -52,7 +52,7 @@ func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc {
}
}
items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GID, repo.AssetID(assetId), int(page), int(pageSize))
items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GroupID, repo.AssetID(assetID), int(page), int(pageSize))
if err != nil {
log.Err(err).Msg("failed to get item")
return validate.NewRequestError(err, http.StatusInternalServerError)

View File

@@ -58,6 +58,25 @@ func GetCookies(r *http.Request) (*CookieContents, error) {
}, nil
}
// AuthProvider is an interface that can be implemented by any authentication provider.
// to extend authentication methods for the API.
type AuthProvider interface {
// Name returns the name of the authentication provider. This should be a unique name.
// that is URL friendly.
//
// Example: "local", "ldap"
Name() string
// Authenticate is called when a user attempts to login to the API. The implementation
// should return an error if the user cannot be authenticated. If an error is returned
// the API controller will return a vague error message to the user.
//
// Authenticate should do the following:
//
// 1. Ensure that the user exists within the database (either create, or get)
// 2. On successful authentication, they must set the user cookies.
Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error)
}
// HandleAuthLogin godoc
//
// @Summary User Login
@@ -66,53 +85,42 @@ func GetCookies(r *http.Request) (*CookieContents, error) {
// @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"
// @Param payload body LoginForm true "Login Data"
// @Param provider query string false "auth provider"
// @Produce json
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin() errchain.HandlerFunc {
func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFunc {
if len(ps) == 0 {
panic("no auth providers provided")
}
providers := make(map[string]AuthProvider)
for _, p := range ps {
log.Info().Str("name", p.Name()).Msg("registering auth provider")
providers[p.Name()] = p
}
return func(w http.ResponseWriter, r *http.Request) error {
loginForm := &LoginForm{}
switch r.Header.Get("Content-Type") {
case "application/x-www-form-urlencoded":
err := r.ParseForm()
if err != nil {
return errors.New("failed to parse form")
}
loginForm.Username = r.PostFormValue("username")
loginForm.Password = r.PostFormValue("password")
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.JSON(w, http.StatusBadRequest, errors.New("invalid content type"))
// Extract provider query
provider := r.URL.Query().Get("provider")
if provider == "" {
provider = "local"
}
if loginForm.Username == "" || loginForm.Password == "" {
return validate.NewFieldErrors(
validate.FieldError{
Field: "username",
Error: "username or password is empty",
},
validate.FieldError{
Field: "password",
Error: "username or password is empty",
},
)
// Get the provider
p, ok := providers[provider]
if !ok {
return validate.NewRequestError(errors.New("invalid auth provider"), http.StatusBadRequest)
}
newToken, err := ctrl.svc.User.Login(r.Context(), strings.ToLower(loginForm.Username), loginForm.Password, loginForm.StayLoggedIn)
newToken, err := p.Authenticate(w, r)
if err != nil {
return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError)
log.Err(err).Msg("failed to authenticate")
return server.JSON(w, http.StatusInternalServerError, err.Error())
}
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, loginForm.StayLoggedIn)
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true)
return server.JSON(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
@@ -145,7 +153,7 @@ func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
}
}
// HandleAuthLogout godoc
// HandleAuthRefresh godoc
//
// @Summary User Token Refresh
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.

View File

@@ -6,13 +6,14 @@ 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/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain"
)
type (
GroupInvitationCreate struct {
Uses int `json:"uses" validate:"required,min=1,max=100"`
Uses int `json:"uses" validate:"required,min=1,max=100"`
ExpiresAt time.Time `json:"expiresAt"`
}
@@ -34,7 +35,7 @@ type (
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 ctrl.repo.Groups.GroupByID(auth, auth.GroupID)
}
return adapters.Command(fn, http.StatusOK)
@@ -52,6 +53,14 @@ func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc {
func (ctrl *V1Controller) HandleGroupUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) {
auth := services.NewContext(r.Context())
ok := ctrl.svc.Currencies.IsSupported(body.Currency)
if !ok {
return repo.Group{}, validate.NewFieldErrors(
validate.NewFieldError("currency", "currency '"+body.Currency+"' is not supported"),
)
}
return ctrl.svc.Group.UpdateGroup(auth, body)
}

View File

@@ -27,7 +27,7 @@ import (
// @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)
// @Param parentIds query []string false "parent Ids" collectionFormat(multi)
// @Param parentIds query []string false "parent Ids" collectionFormat(multi)
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET]
// @Security Bearer
@@ -57,7 +57,7 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
Search: params.Get("q"),
LocationIDs: queryUUIDList(params, "locations"),
LabelIDs: queryUUIDList(params, "labels"),
ParentItemIDs: queryUUIDList(params, "parentIds"),
ParentItemIDs: queryUUIDList(params, "parentIds"),
IncludeArchived: queryBool(params.Get("includeArchived")),
Fields: filterFieldItems(params["fields"]),
OrderBy: params.Get("orderBy"),
@@ -79,7 +79,7 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
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))
items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GroupID, extractQuery(r))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{
@@ -93,6 +93,48 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
}
}
// HandleItemFullPath godoc
//
// @Summary Get the full path of an item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 200 {object} []repo.ItemPath
// @Router /v1/items/{id}/path [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemFullPath() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) ([]repo.ItemPath, error) {
auth := services.NewContext(r.Context())
item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GroupID, ID)
if err != nil {
return nil, err
}
paths, err := ctrl.repo.Locations.PathForLoc(auth, auth.GroupID, item.Location.ID)
if err != nil {
return nil, err
}
if item.Parent != nil {
paths = append(paths, repo.ItemPath{
Type: repo.ItemTypeItem,
ID: item.Parent.ID,
Name: item.Parent.Name,
})
}
paths = append(paths, repo.ItemPath{
Type: repo.ItemTypeItem,
ID: item.ID,
Name: item.Name,
})
return paths, nil
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleItemsCreate godoc
//
// @Summary Create Item
@@ -123,7 +165,7 @@ 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 ctrl.repo.Items.GetOneByGroup(auth, auth.GroupID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
@@ -141,7 +183,7 @@ func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc {
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)
err := ctrl.repo.Items.DeleteByGroup(auth, auth.GroupID, ID)
return nil, err
}
@@ -163,7 +205,7 @@ func (ctrl *V1Controller) HandleItemUpdate() errchain.HandlerFunc {
auth := services.NewContext(r.Context())
body.ID = ID
return ctrl.repo.Items.UpdateByGroup(auth, auth.GID, body)
return ctrl.repo.Items.UpdateByGroup(auth, auth.GroupID, body)
}
return adapters.ActionID("id", fn, http.StatusOK)
@@ -184,12 +226,12 @@ func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc {
auth := services.NewContext(r.Context())
body.ID = ID
err := ctrl.repo.Items.Patch(auth, auth.GID, ID, body)
err := ctrl.repo.Items.Patch(auth, auth.GroupID, ID, body)
if err != nil {
return repo.ItemOut{}, err
}
return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
return ctrl.repo.Items.GetOneByGroup(auth, auth.GroupID, ID)
}
return adapters.ActionID("id", fn, http.StatusOK)
@@ -207,7 +249,7 @@ func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc {
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 ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GroupID)
}
return adapters.Command(fn, http.StatusOK)
@@ -229,11 +271,10 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc {
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 ctrl.repo.Items.GetAllCustomFieldValues(auth, auth.GroupID, q.Field)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleItemsImport godocs
@@ -282,7 +323,7 @@ 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)
csvData, err := ctrl.svc.Items.ExportTSV(r.Context(), ctx.GroupID)
if err != nil {
log.Err(err).Msg("failed to export items")
return validate.NewRequestError(err, http.StatusInternalServerError)

View File

@@ -4,6 +4,7 @@ import (
"errors"
"net/http"
"path/filepath"
"strings"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
@@ -39,7 +40,6 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
if err != nil {
log.Err(err).Msg("failed to parse multipart form")
return validate.NewRequestError(errors.New("failed to parse multipart form"), http.StatusBadRequest)
}
errs := validate.NewFieldErrors()
@@ -71,7 +71,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
// Attempt to auto-detect the type of the file
ext := filepath.Ext(attachmentName)
switch ext {
switch strings.ToLower(ext) {
case ".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff":
attachmentType = attachment.TypePhoto.String()
default:
@@ -168,7 +168,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
// Delete Attachment Handler
case http.MethodDelete:
err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentID)
err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GroupID, ID, attachmentID)
if err != nil {
log.Err(err).Msg("failed to delete attachment")
return validate.NewRequestError(err, http.StatusInternalServerError)

View File

@@ -21,7 +21,7 @@ import (
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 ctrl.repo.Labels.GetAll(auth, auth.GroupID)
}
return adapters.Command(fn, http.StatusOK)
@@ -39,7 +39,7 @@ func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc {
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 ctrl.repo.Labels.Create(auth, auth.GroupID, data)
}
return adapters.Action(fn, http.StatusCreated)
@@ -57,7 +57,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc {
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)
err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GroupID, ID)
return nil, err
}
@@ -76,7 +76,7 @@ func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc {
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 ctrl.repo.Labels.GetOneByGroup(auth, auth.GroupID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
@@ -95,7 +95,7 @@ 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 ctrl.repo.Labels.UpdateByGroup(auth, auth.GroupID, data)
}
return adapters.ActionID("id", fn, http.StatusOK)

View File

@@ -10,7 +10,7 @@ import (
"github.com/hay-kot/httpkit/errchain"
)
// HandleLocationTreeQuery
// HandleLocationTreeQuery godoc
//
// @Summary Get Locations Tree
// @Tags Locations
@@ -22,13 +22,13 @@ import (
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 ctrl.repo.Locations.Tree(auth, auth.GroupID, query)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleLocationGetAll
// HandleLocationGetAll godoc
//
// @Summary Get All Locations
// @Tags Locations
@@ -40,13 +40,13 @@ func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc {
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 ctrl.repo.Locations.GetAll(auth, auth.GroupID, q)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleLocationCreate
// HandleLocationCreate godoc
//
// @Summary Create Location
// @Tags Locations
@@ -58,13 +58,13 @@ func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc {
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 ctrl.repo.Locations.Create(auth, auth.GroupID, createData)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleLocationDelete
// HandleLocationDelete godoc
//
// @Summary Delete Location
// @Tags Locations
@@ -76,14 +76,14 @@ func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc {
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)
err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GroupID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleLocationGet
// HandleLocationGet godoc
//
// @Summary Get Location
// @Tags Locations
@@ -95,13 +95,13 @@ func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
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 ctrl.repo.Locations.GetOneByGroup(auth, auth.GroupID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleLocationUpdate
// HandleLocationUpdate godoc
//
// @Summary Update Location
// @Tags Locations
@@ -115,7 +115,7 @@ 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 ctrl.repo.Locations.UpdateByGroup(auth, auth.GroupID, ID, body)
}
return adapters.ActionID("id", fn, http.StatusOK)

View File

@@ -10,7 +10,7 @@ import (
"github.com/hay-kot/httpkit/errchain"
)
// HandleMaintenanceGetLog godoc
// HandleMaintenanceLogGet godoc
//
// @Summary Get Maintenance Log
// @Tags Maintenance
@@ -21,7 +21,7 @@ import (
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 ctrl.repo.MaintEntry.GetLog(auth, auth.GroupID, ID, q)
}
return adapters.QueryID("id", fn, http.StatusOK)

View File

@@ -40,7 +40,7 @@ func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc {
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 ctrl.repo.Notifiers.Create(auth, auth.GroupID, auth.UserID, in)
}
return adapters.Action(fn, http.StatusCreated)
@@ -57,7 +57,7 @@ func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc {
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 nil, ctrl.repo.Notifiers.Delete(auth, auth.UserID, ID)
}
return adapters.CommandID("id", fn, http.StatusNoContent)
@@ -75,7 +75,7 @@ func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc {
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 ctrl.repo.Notifiers.Update(auth, auth.UserID, ID, in)
}
return adapters.ActionID("id", fn, http.StatusOK)

View File

@@ -12,7 +12,7 @@ import (
"github.com/hay-kot/httpkit/server"
)
// HandleGroupGet godoc
// HandleGroupStatisticsLocations godoc
//
// @Summary Get Location Statistics
// @Tags Statistics
@@ -23,7 +23,7 @@ import (
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 ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GroupID)
}
return adapters.Command(fn, http.StatusOK)
@@ -40,7 +40,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc
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 ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GroupID)
}
return adapters.Command(fn, http.StatusOK)
@@ -57,7 +57,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc {
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 ctrl.repo.Groups.StatsGroup(auth, auth.GroupID)
}
return adapters.Command(fn, http.StatusOK)
@@ -94,7 +94,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerF
return validate.NewRequestError(err, http.StatusBadRequest)
}
stats, err := ctrl.repo.Groups.StatsPurchasePrice(ctx, ctx.GID, startDate, endDate)
stats, err := ctrl.repo.Groups.StatsPurchasePrice(ctx, ctx.GroupID, startDate, endDate)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}

View File

@@ -1,6 +1,7 @@
package v1
import (
"context"
"fmt"
"net/http"
@@ -8,6 +9,7 @@ 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/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
@@ -115,12 +117,10 @@ func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc {
}
}
type (
ChangePassword struct {
Current string `json:"current,omitempty"`
New string `json:"new,omitempty"`
}
)
type ChangePassword struct {
Current string `json:"current,omitempty"`
New string `json:"new,omitempty"`
}
// HandleUserSelfChangePassword godoc
//
@@ -144,7 +144,7 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc {
ctx := services.NewContext(r.Context())
ok := ctrl.svc.User.ChangePassword(ctx, cp.Current, cp.New)
ok := ctrl.svc.User.PasswordChange(ctx, cp.Current, cp.New)
if !ok {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
@@ -152,3 +152,73 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc {
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleUserSelfChangePasswordWithToken godoc
//
// @Summary Change Password
// @Tags User
// @Success 204
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password-token [PUT]
func (ctrl *V1Controller) HandleUserSelfChangePasswordWithToken() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
tokenQueryParam := r.URL.Query().Get("token")
if tokenQueryParam == "" {
return validate.NewRequestError(fmt.Errorf("missing token query param"), http.StatusBadRequest)
}
if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden)
}
var cp ChangePassword
err := server.Decode(r, &cp)
if err != nil {
log.Err(err).Msg("user failed to change password")
}
ctx := services.NewContext(r.Context())
ok := ctrl.svc.User.PasswordChange(ctx, cp.Current, cp.New)
if !ok {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleUserRequestPasswordReset godoc
//
// @Summary Request Password Reset
// @Tags User
// @Produce json
// @Param payload body services.PasswordResetRequest true "User Data"
// @Success 204
// @Router /v1/users/request-password-reset [Post]
func (ctrl *V1Controller) HandleUserRequestPasswordReset() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden)
}
v, err := adapters.DecodeBody[services.PasswordResetRequest](r)
if err != nil {
return err
}
go func() {
ctx := context.Background()
err = ctrl.svc.User.PasswordResetRequest(ctx, v)
if err != nil {
log.Warn().
Err(err).
Str("email", v.Email).
Msg("failed to request password reset")
}
}()
return server.JSON(w, http.StatusNoContent, nil)
}
}

View File

@@ -2,7 +2,6 @@ package main
import (
"os"
"strings"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/rs/zerolog"
@@ -18,24 +17,8 @@ func (a *app) setupLogger() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Caller().Logger()
}
log.Level(getLevel(a.conf.Log.Level))
}
func getLevel(l string) zerolog.Level {
switch strings.ToLower(l) {
case "debug":
return zerolog.DebugLevel
case "info":
return zerolog.InfoLevel
case "warn":
return zerolog.WarnLevel
case "error":
return zerolog.ErrorLevel
case "fatal":
return zerolog.FatalLevel
case "panic":
return zerolog.PanicLevel
default:
return zerolog.InfoLevel
level, err := zerolog.ParseLevel(a.conf.Log.Level)
if err == nil {
zerolog.SetGlobalLevel(level)
}
}

View File

@@ -1,6 +1,7 @@
package main
import (
"bytes"
"context"
"fmt"
"net/http"
@@ -13,6 +14,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/hay-kot/homebox/backend/internal/core/currencies"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/hay-kot/homebox/backend/internal/data/ent"
@@ -21,7 +23,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/hay-kot/httpkit/graceful"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
@@ -78,12 +80,12 @@ func run(cfg *config.Config) error {
log.Fatal().Err(err).Msg("failed to create data directory")
}
c, err := ent.Open("sqlite3", cfg.Storage.SqliteUrl)
c, err := ent.Open("sqlite3", cfg.Storage.SqliteURL)
if err != nil {
log.Fatal().
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteUrl).
Str("url", cfg.Storage.SqliteURL).
Msg("failed opening connection to sqlite")
}
defer func(c *ent.Client) {
@@ -113,11 +115,11 @@ func run(cfg *config.Config) error {
err = c.Schema.Create(context.Background(), options...)
if err != nil {
log.Fatal().
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteUrl).
Msg("failed creating schema resources")
log.Fatal(). // nolint
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL).
Msg("failed creating schema resources")
}
err = os.RemoveAll(temp)
@@ -126,12 +128,42 @@ func run(cfg *config.Config) error {
return err
}
collectFuncs := []currencies.CollectorFunc{
currencies.CollectDefaults(),
}
if cfg.Options.CurrencyConfig != "" {
log.Info().
Str("path", cfg.Options.CurrencyConfig).
Msg("loading currency config file")
content, err := os.ReadFile(cfg.Options.CurrencyConfig)
if err != nil {
log.Fatal().
Err(err).
Str("path", cfg.Options.CurrencyConfig).
Msg("failed to read currency config file")
}
collectFuncs = append(collectFuncs, currencies.CollectJSON(bytes.NewReader(content)))
}
currencies, err := currencies.CollectionCurrencies(collectFuncs...)
if err != nil {
log.Fatal().
Err(err).
Msg("failed to collect currencies")
}
app.bus = eventbus.New()
app.db = c
app.repos = repo.New(c, app.bus, cfg.Storage.Data)
app.services = services.New(
app.repos,
app.conf.BaseURL,
app.mailer,
services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID),
services.WithCurrencies(currencies),
)
// =========================================================================
@@ -148,41 +180,63 @@ func run(cfg *config.Config) error {
middleware.StripSlashes,
)
chain := errchain.New(mid.Errors(app.server, logger))
chain := errchain.New(mid.Errors(logger))
app.mountRoutes(router, chain, app.repos)
app.mountRoutes(router, chain)
app.server = server.NewServer(
server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port),
server.WithReadTimeout(app.conf.Web.ReadTimeout),
server.WithWriteTimeout(app.conf.Web.WriteTimeout),
server.WithIdleTimeout(app.conf.Web.IdleTimeout),
)
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
runner := graceful.NewRunner()
runner.AddFunc("server", func(ctx context.Context) error {
httpserver := http.Server{
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Web.Port),
Handler: router,
ReadTimeout: cfg.Web.ReadTimeout,
WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
}
go func() {
<-ctx.Done()
_ = httpserver.Shutdown(context.Background())
}()
log.Info().Msgf("Server is running on %s:%s", cfg.Web.Host, cfg.Web.Port)
return httpserver.ListenAndServe()
})
// =========================================================================
// Start Reoccurring Tasks
go app.bus.Run()
runner.AddFunc("eventbus", app.bus.Run)
go app.startBgTask(time.Duration(24)*time.Hour, func() {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background())
runner.AddFunc("seed_database", func(ctx context.Context) error {
// TODO: Remove through external API that does setup
if cfg.Demo {
log.Info().Msg("Running in demo mode, creating demo data")
app.SetupDemo()
}
return nil
})
runner.AddPlugin(NewTask("purge-tokens", time.Duration(24)*time.Hour, func(ctx context.Context) {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(ctx)
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired tokens")
}
})
go app.startBgTask(time.Duration(24)*time.Hour, func() {
_, err := app.repos.Groups.InvitationPurge(context.Background())
}))
runner.AddPlugin(NewTask("purge-invitations", time.Duration(24)*time.Hour, func(ctx context.Context) {
_, err := app.repos.Groups.InvitationPurge(ctx)
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired invitations")
}
})
go app.startBgTask(time.Duration(1)*time.Hour, func() {
}))
runner.AddPlugin(NewTask("send-notifications", time.Duration(1)*time.Hour, func(ctx context.Context) {
now := time.Now()
if now.Hour() == 8 {
@@ -194,22 +248,27 @@ func run(cfg *config.Config) error {
Msg("failed to send notifiers")
}
}
})
// TODO: Remove through external API that does setup
if cfg.Demo {
log.Info().Msg("Running in demo mode, creating demo data")
app.SetupDemo()
}
}))
if cfg.Debug.Enabled {
debugrouter := app.debugRouter()
go func() {
if err := http.ListenAndServe(":"+cfg.Debug.Port, debugrouter); err != nil {
log.Fatal().Err(err).Msg("failed to start debug server")
runner.AddFunc("debug", func(ctx context.Context) error {
debugserver := http.Server{
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Debug.Port),
Handler: app.debugRouter(),
ReadTimeout: cfg.Web.ReadTimeout,
WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
}
}()
go func() {
<-ctx.Done()
_ = debugserver.Shutdown(context.Background())
}()
log.Info().Msgf("Debug server is running on %s:%s", cfg.Web.Host, cfg.Debug.Port)
return debugserver.ListenAndServe()
})
}
return app.server.Start(router)
return runner.Start(context.Background())
}

View File

@@ -9,6 +9,7 @@ import (
v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1"
"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/sys/validate"
"github.com/hay-kot/httpkit/errchain"
)
@@ -130,7 +131,7 @@ func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler {
}
if requestToken == "" {
return validate.NewRequestError(errors.New("Authorization header or query is required"), http.StatusUnauthorized)
return validate.NewRequestError(errors.New("authorization header or query is required"), http.StatusUnauthorized)
}
requestToken = strings.TrimPrefix(requestToken, "Bearer ")
@@ -140,7 +141,11 @@ func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler {
usr, err := a.services.User.GetSelf(r.Context(), requestToken)
// Check the database for the token
if err != nil {
return validate.NewRequestError(errors.New("valid authorization header is required"), http.StatusUnauthorized)
if ent.IsNotFound(err) {
return validate.NewRequestError(errors.New("valid authorization token is required"), http.StatusUnauthorized)
}
return err
}
r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken))

View File

@@ -0,0 +1,2 @@
// Package providers provides a authentication abstraction for the backend.
package providers

View File

@@ -0,0 +1,55 @@
package providers
import (
"errors"
"net/http"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
)
type LoginForm struct {
Username string `json:"username"`
Password string `json:"password"`
StayLoggedIn bool `json:"stayLoggedIn"`
}
func getLoginForm(r *http.Request) (LoginForm, error) {
loginForm := LoginForm{}
switch r.Header.Get("Content-Type") {
case "application/x-www-form-urlencoded":
err := r.ParseForm()
if err != nil {
return loginForm, errors.New("failed to parse form")
}
loginForm.Username = r.PostFormValue("username")
loginForm.Password = r.PostFormValue("password")
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 loginForm, errors.New("failed to decode login form")
}
default:
return loginForm, errors.New("invalid content type")
}
if loginForm.Username == "" || loginForm.Password == "" {
return loginForm, validate.NewFieldErrors(
validate.FieldError{
Field: "username",
Error: "username or password is empty",
},
validate.FieldError{
Field: "password",
Error: "username or password is empty",
},
)
}
return loginForm, nil
}

View File

@@ -0,0 +1,30 @@
package providers
import (
"net/http"
"github.com/hay-kot/homebox/backend/internal/core/services"
)
type LocalProvider struct {
service *services.UserService
}
func NewLocalProvider(service *services.UserService) *LocalProvider {
return &LocalProvider{
service: service,
}
}
func (p *LocalProvider) Name() string {
return "local"
}
func (p *LocalProvider) Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) {
loginForm, err := getLoginForm(r)
if err != nil {
return services.UserAuthTokenDetail{}, err
}
return p.service.Login(r.Context(), loginForm.Username, loginForm.Password, loginForm.StayLoggedIn)
}

View File

@@ -12,11 +12,11 @@ import (
"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/providers"
_ "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/httpkit/errchain"
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware
)
const prefix = "/api"
@@ -36,7 +36,7 @@ func (a *app) debugRouter() *http.ServeMux {
}
// registerRoutes registers all the routes for the API
func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllRepos) {
func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain) {
registerMimes()
r.Get("/swagger/*", httpSwagger.Handler(
@@ -46,7 +46,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
// =========================================================================
// API Version 1
v1Base := v1.BaseUrlFunc(prefix)
v1Base := v1.BaseURLFunc(prefix)
v1Ctrl := v1.NewControllerV1(
a.services,
@@ -63,8 +63,15 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
BuildTime: buildTime,
})))
r.Get(v1Base("/currencies"), chain.ToHandlerFunc(v1Ctrl.HandleCurrency()))
providers := []v1.AuthProvider{
providers.NewLocalProvider(a.services.User),
}
r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin()))
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
r.Post(v1Base("/users/request-password-reset"), chain.ToHandlerFunc(v1Ctrl.HandleUserRequestPasswordReset()))
userMW := []errchain.Middleware{
a.mwAuthToken,
@@ -78,6 +85,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
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...))
r.Put(v1Base("/users/self/change-password-token"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePasswordWithToken()))
r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
@@ -115,6 +123,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...))
r.Get(v1Base("/items/{id}/path"), chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...))
r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...))
r.Patch(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...))
r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
@@ -178,7 +187,7 @@ func notFoundHandler() errchain.HandlerFunc {
if err != nil {
return err
}
defer f.Close()
defer func() { _ = f.Close() }()
stat, _ := f.Stat()
if stat.IsDir() {

View File

@@ -150,6 +150,25 @@ const docTemplate = `{
}
}
},
"/v1/currency": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Base"
],
"summary": "Currency",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/currencies.Currency"
}
}
}
}
},
"/v1/groups": {
"get": {
"security": [
@@ -410,6 +429,16 @@ const docTemplate = `{
"description": "location Ids",
"name": "locations",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "parent Ids",
"name": "parentIds",
"in": "query"
}
],
"responses": {
@@ -988,6 +1017,42 @@ const docTemplate = `{
}
}
},
"/v1/items/{id}/path": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get the full path of an item",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemPath"
}
}
}
}
}
},
"/v1/labels": {
"get": {
"security": [
@@ -1574,7 +1639,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.ApiSummary"
"$ref": "#/definitions/v1.APISummary"
}
}
}
@@ -1645,6 +1710,12 @@ const docTemplate = `{
"schema": {
"$ref": "#/definitions/v1.LoginForm"
}
},
{
"type": "string",
"description": "auth provider",
"name": "provider",
"in": "query"
}
],
"responses": {
@@ -1721,6 +1792,33 @@ const docTemplate = `{
}
}
},
"/v1/users/request-password-reset": {
"post": {
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Request Password Reset",
"parameters": [
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/services.PasswordResetRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/users/self": {
"get": {
"security": [
@@ -1823,6 +1921,23 @@ const docTemplate = `{
}
},
"definitions": {
"currencies.Currency": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"local": {
"type": "string"
},
"name": {
"type": "string"
},
"symbol": {
"type": "string"
}
}
},
"repo.DocumentOut": {
"type": "object",
"properties": {
@@ -1998,12 +2113,6 @@ const docTemplate = `{
"$ref": "#/definitions/repo.ItemAttachment"
}
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": {
"type": "string"
},
@@ -2122,6 +2231,20 @@ const docTemplate = `{
}
}
},
"repo.ItemPath": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"$ref": "#/definitions/repo.ItemType"
}
}
},
"repo.ItemSummary": {
"type": "object",
"properties": {
@@ -2174,6 +2297,17 @@ const docTemplate = `{
}
}
},
"repo.ItemType": {
"type": "string",
"enum": [
"location",
"item"
],
"x-enum-varnames": [
"ItemTypeLocation",
"ItemTypeItem"
]
},
"repo.ItemUpdate": {
"type": "object",
"properties": {
@@ -2181,8 +2315,7 @@ const docTemplate = `{
"type": "boolean"
},
"assetId": {
"type": "string",
"example": "0"
"type": "string"
},
"description": {
"type": "string"
@@ -2719,6 +2852,14 @@ const docTemplate = `{
}
}
},
"services.PasswordResetRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
}
}
},
"services.UserRegistration": {
"type": "object",
"properties": {
@@ -2736,15 +2877,7 @@ const docTemplate = `{
}
}
},
"v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.ApiSummary": {
"v1.APISummary": {
"type": "object",
"properties": {
"allowRegistration": {
@@ -2773,6 +2906,14 @@ const docTemplate = `{
}
}
},
"v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.Build": {
"type": "object",
"properties": {

View File

@@ -143,6 +143,25 @@
}
}
},
"/v1/currency": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Base"
],
"summary": "Currency",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/currencies.Currency"
}
}
}
}
},
"/v1/groups": {
"get": {
"security": [
@@ -403,6 +422,16 @@
"description": "location Ids",
"name": "locations",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "parent Ids",
"name": "parentIds",
"in": "query"
}
],
"responses": {
@@ -981,6 +1010,42 @@
}
}
},
"/v1/items/{id}/path": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get the full path of an item",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemPath"
}
}
}
}
}
},
"/v1/labels": {
"get": {
"security": [
@@ -1567,7 +1632,7 @@
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.ApiSummary"
"$ref": "#/definitions/v1.APISummary"
}
}
}
@@ -1638,6 +1703,12 @@
"schema": {
"$ref": "#/definitions/v1.LoginForm"
}
},
{
"type": "string",
"description": "auth provider",
"name": "provider",
"in": "query"
}
],
"responses": {
@@ -1714,6 +1785,33 @@
}
}
},
"/v1/users/request-password-reset": {
"post": {
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Request Password Reset",
"parameters": [
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/services.PasswordResetRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/users/self": {
"get": {
"security": [
@@ -1816,6 +1914,23 @@
}
},
"definitions": {
"currencies.Currency": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"local": {
"type": "string"
},
"name": {
"type": "string"
},
"symbol": {
"type": "string"
}
}
},
"repo.DocumentOut": {
"type": "object",
"properties": {
@@ -1991,12 +2106,6 @@
"$ref": "#/definitions/repo.ItemAttachment"
}
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": {
"type": "string"
},
@@ -2115,6 +2224,20 @@
}
}
},
"repo.ItemPath": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"$ref": "#/definitions/repo.ItemType"
}
}
},
"repo.ItemSummary": {
"type": "object",
"properties": {
@@ -2167,6 +2290,17 @@
}
}
},
"repo.ItemType": {
"type": "string",
"enum": [
"location",
"item"
],
"x-enum-varnames": [
"ItemTypeLocation",
"ItemTypeItem"
]
},
"repo.ItemUpdate": {
"type": "object",
"properties": {
@@ -2174,8 +2308,7 @@
"type": "boolean"
},
"assetId": {
"type": "string",
"example": "0"
"type": "string"
},
"description": {
"type": "string"
@@ -2712,6 +2845,14 @@
}
}
},
"services.PasswordResetRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
}
}
},
"services.UserRegistration": {
"type": "object",
"properties": {
@@ -2729,15 +2870,7 @@
}
}
},
"v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.ApiSummary": {
"v1.APISummary": {
"type": "object",
"properties": {
"allowRegistration": {
@@ -2766,6 +2899,14 @@
}
}
},
"v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.Build": {
"type": "object",
"properties": {

View File

@@ -1,5 +1,16 @@
basePath: /api
definitions:
currencies.Currency:
properties:
code:
type: string
local:
type: string
name:
type: string
symbol:
type: string
type: object
repo.DocumentOut:
properties:
id:
@@ -116,10 +127,6 @@ definitions:
items:
$ref: '#/definitions/repo.ItemAttachment'
type: array
children:
items:
$ref: '#/definitions/repo.ItemSummary'
type: array
createdAt:
type: string
description:
@@ -199,6 +206,15 @@ definitions:
x-nullable: true
x-omitempty: true
type: object
repo.ItemPath:
properties:
id:
type: string
name:
type: string
type:
$ref: '#/definitions/repo.ItemType'
type: object
repo.ItemSummary:
properties:
archived:
@@ -233,12 +249,19 @@ definitions:
updatedAt:
type: string
type: object
repo.ItemType:
enum:
- location
- item
type: string
x-enum-varnames:
- ItemTypeLocation
- ItemTypeItem
repo.ItemUpdate:
properties:
archived:
type: boolean
assetId:
example: "0"
type: string
description:
type: string
@@ -597,6 +620,11 @@ definitions:
value:
type: number
type: object
services.PasswordResetRequest:
properties:
email:
type: string
type: object
services.UserRegistration:
properties:
email:
@@ -608,12 +636,7 @@ definitions:
token:
type: string
type: object
v1.ActionAmountResult:
properties:
completed:
type: integer
type: object
v1.ApiSummary:
v1.APISummary:
properties:
allowRegistration:
type: boolean
@@ -632,6 +655,11 @@ definitions:
type: string
type: array
type: object
v1.ActionAmountResult:
properties:
completed:
type: integer
type: object
v1.Build:
properties:
buildTime:
@@ -789,6 +817,18 @@ paths:
summary: Get Item by Asset ID
tags:
- Items
/v1/currency:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/currencies.Currency'
summary: Currency
tags:
- Base
/v1/groups:
get:
produces:
@@ -942,6 +982,13 @@ paths:
type: string
name: locations
type: array
- collectionFormat: multi
description: parent Ids
in: query
items:
type: string
name: parentIds
type: array
produces:
- application/json
responses:
@@ -1239,6 +1286,28 @@ paths:
summary: Update Maintenance Entry
tags:
- Maintenance
/v1/items/{id}/path:
get:
parameters:
- description: Item ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.ItemPath'
type: array
security:
- Bearer: []
summary: Get the full path of an item
tags:
- Items
/v1/items/export:
get:
responses:
@@ -1656,7 +1725,7 @@ paths:
"200":
description: OK
schema:
$ref: '#/definitions/v1.ApiSummary'
$ref: '#/definitions/v1.APISummary'
summary: Application Info
tags:
- Base
@@ -1699,6 +1768,10 @@ paths:
required: true
schema:
$ref: '#/definitions/v1.LoginForm'
- description: auth provider
in: query
name: provider
type: string
produces:
- application/json
responses:
@@ -1749,6 +1822,23 @@ paths:
summary: Register New User
tags:
- User
/v1/users/request-password-reset:
post:
parameters:
- description: User Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/services.PasswordResetRequest'
produces:
- application/json
responses:
"204":
description: No Content
summary: Request Password Reset
tags:
- User
/v1/users/self:
delete:
produces:

View File

@@ -71,7 +71,7 @@ func main() {
text = replace.Regex.ReplaceAllString(text, replace.Text)
}
err = os.WriteFile(path, []byte(text), 0644)
err = os.WriteFile(path, []byte(text), 0o644)
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@@ -1,29 +1,32 @@
module github.com/hay-kot/homebox/backend
go 1.20
go 1.22
toolchain go1.22.0
require (
ariga.io/atlas v0.15.0
ariga.io/atlas v0.19.1
entgo.io/ent v0.12.5
github.com/ardanlabs/conf/v3 v3.1.7
github.com/containrrr/shoutrrr v0.8.0
github.com/go-chi/chi/v5 v5.0.10
github.com/go-playground/validator/v10 v10.16.0
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d
github.com/google/uuid v1.4.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-playground/validator/v10 v10.18.0
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/google/uuid v1.6.0
github.com/gorilla/schema v1.2.1
github.com/hay-kot/httpkit v0.0.3
github.com/mattn/go-sqlite3 v1.14.18
github.com/hay-kot/easyemails v0.0.0-20240206011027-25232fb79aa3
github.com/hay-kot/httpkit v0.0.9
github.com/mattn/go-sqlite3 v1.14.22
github.com/olahol/melody v1.1.4
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.31.0
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.8.4
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.16.2
github.com/swaggo/http-swagger/v2 v2.0.2
github.com/swaggo/swag v1.16.3
github.com/yeqown/go-qrcode/v2 v2.2.2
github.com/yeqown/go-qrcode/writer/standard v1.2.2
golang.org/x/crypto v0.15.0
modernc.org/sqlite v1.27.0
golang.org/x/crypto v0.19.0
modernc.org/sqlite v1.29.2
)
require (
@@ -45,34 +48,32 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl/v2 v2.19.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/leodido/go-urn v1.4.0 // 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.20 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/swaggo/files v1.0.1 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/zclconf/go-cty v1.14.1 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.15.0 // indirect
golang.org/x/tools v0.17.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.15 // indirect
modernc.org/libc v1.34.4 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.41.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)

View File

@@ -1,8 +1,9 @@
ariga.io/atlas v0.15.0 h1:9lwSVcO/D3WgaCzstSGqR1hEDtsGibu6JqUofEI/0sY=
ariga.io/atlas v0.15.0/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw=
ariga.io/atlas v0.19.1 h1:QzBHkakwzEhmPWOzNhw8Yr/Bbicj6Iq5hwEoNI/Jr9A=
ariga.io/atlas v0.19.1/go.mod h1:VPlcXdd4w2KqKnH54yEZcry79UAhpaWaxEsmn5JRNoE=
entgo.io/ent v0.12.5 h1:KREM5E4CSoej4zeGa88Ou/gfturAnpUv0mzAjch1sj4=
entgo.io/ent v0.12.5/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
@@ -11,6 +12,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/ardanlabs/conf/v3 v3.1.7 h1:p232cF68TafoA5U9ZlbxUIhGJtGNdKHBXF80Fdqb5t0=
github.com/ardanlabs/conf/v3 v3.1.7/go.mod h1:zclexWKe0NVj6LHQ8NgDDZ7bQ1spE0KeKPFficdtAjU=
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y=
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk=
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@@ -26,9 +29,10 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -47,49 +51,59 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d h1:KbPOUXFUDJxwZ04vbmDOc3yuruGvVO+LOa7cVER3yWw=
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM=
github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hay-kot/httpkit v0.0.3 h1:QYq01J5Jrn+ie0s1ptavNSEyydkOHqsrw4RLp+2LeJQ=
github.com/hay-kot/httpkit v0.0.3/go.mod h1:1s/OJwWRyH6tBtTw76jTp6kwBYvjswziXaokPQH7eKQ=
github.com/hay-kot/easyemails v0.0.0-20240206011027-25232fb79aa3 h1:EljujAOvukSS+sh8e883j4vhEiYG4EvJFAhl+XvUYe8=
github.com/hay-kot/easyemails v0.0.0-20240206011027-25232fb79aa3/go.mod h1:SZdhYMO2ZmEuTTDE7pHn0WanXhwteniOCYsQ3B5IT/o=
github.com/hay-kot/httpkit v0.0.9 h1:hu2TPY9awmIYWXxWGubaXl2U61pPvaVsm9YwboBRGu0=
github.com/hay-kot/httpkit v0.0.9/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@@ -101,15 +115,19 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olahol/melody v1.1.4 h1:RQHfKZkQmDxI0+SLZRNBCn4LiXdqxLKRGSkT8Dyoe/E=
github.com/olahol/melody v1.1.4/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -119,9 +137,10 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -130,69 +149,41 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/yeqown/go-qrcode/v2 v2.2.2 h1:0comk6jEwi0oWNhKEmzx4JI+Q7XIneAApmFSMKWmSVc=
github.com/yeqown/go-qrcode/v2 v2.2.2/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24=
github.com/yeqown/go-qrcode/writer/standard v1.2.2 h1:gyzunKXgC0ZUpKqQFUImbAEwewAiwNCkxFEKZV80Kt4=
github.com/yeqown/go-qrcode/writer/standard v1.2.2/go.mod h1:bbVRiBJSRPj4UBZP/biLG7JSd9kHqXjErk1eakAMnRA=
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -205,27 +196,17 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.34.4 h1:r9+5s4wNeoCsB8CuJE67UB4N07ernbvrcry9O3MLWtQ=
modernc.org/libc v1.34.4/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/sqlite v1.29.2 h1:xgBSyA3gemwgP31PWFfFjtBorQNYpeypGdoSDjXhrgI=
modernc.org/sqlite v1.29.2/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=

View File

@@ -0,0 +1,104 @@
// Package currencies provides a shared definition of currencies. This uses a global
// variable to hold the currencies.
package currencies
import (
"bytes"
_ "embed"
"encoding/json"
"io"
"slices"
"strings"
"sync"
)
//go:embed currencies.json
var defaults []byte
type CollectorFunc func() ([]Currency, error)
func CollectJSON(reader io.Reader) CollectorFunc {
return func() ([]Currency, error) {
var currencies []Currency
err := json.NewDecoder(reader).Decode(&currencies)
if err != nil {
return nil, err
}
return currencies, nil
}
}
func CollectDefaults() CollectorFunc {
return CollectJSON(bytes.NewReader(defaults))
}
func CollectionCurrencies(collectors ...CollectorFunc) ([]Currency, error) {
out := make([]Currency, 0, len(collectors))
for i := range collectors {
c, err := collectors[i]()
if err != nil {
return nil, err
}
out = append(out, c...)
}
return out, nil
}
type Currency struct {
Name string `json:"name"`
Code string `json:"code"`
Local string `json:"local"`
Symbol string `json:"symbol"`
}
type CurrencyRegistry struct {
mu sync.RWMutex
registry map[string]Currency
}
func NewCurrencyService(currencies []Currency) *CurrencyRegistry {
registry := make(map[string]Currency, len(currencies))
for i := range currencies {
registry[currencies[i].Code] = currencies[i]
}
return &CurrencyRegistry{
registry: registry,
}
}
func (cs *CurrencyRegistry) Slice() []Currency {
cs.mu.RLock()
defer cs.mu.RUnlock()
out := make([]Currency, 0, len(cs.registry))
for key := range cs.registry {
out = append(out, cs.registry[key])
}
slices.SortFunc(out, func(a, b Currency) int {
if a.Name < b.Name {
return -1
}
if a.Name > b.Name {
return 1
}
return 0
})
return out
}
func (cs *CurrencyRegistry) IsSupported(code string) bool {
upper := strings.ToUpper(code)
cs.mu.RLock()
defer cs.mu.RUnlock()
_, ok := cs.registry[upper]
return ok
}

View File

@@ -0,0 +1,638 @@
[
{
"code": "USD",
"local": "United States",
"symbol": "$",
"name": "United States Dollar"
},
{
"code": "AED",
"local": "United Arab Emirates",
"symbol": "د.إ",
"name": "United Arab Emirates Dirham"
},
{
"code": "AFN",
"local": "Afghanistan",
"symbol": "؋",
"name": "Afghan Afghani"
},
{
"code": "ALL",
"local": "Albania",
"symbol": "L",
"name": "Albanian Lek"
},
{
"code": "AMD",
"local": "Armenia",
"symbol": "֏",
"name": "Armenian Dram"
},
{
"code": "ANG",
"local": "Netherlands Antilles",
"symbol": "ƒ",
"name": "Netherlands Antillean Guilder"
},
{
"code": "AOA",
"local": "Angola",
"symbol": "Kz",
"name": "Angolan Kwanza"
},
{
"code": "ARS",
"local": "Argentina",
"symbol": "$",
"name": "Argentine Peso"
},
{
"code": "AUD",
"local": "Australia",
"symbol": "A$",
"name": "Australian Dollar"
},
{
"code": "AWG",
"local": "Aruba",
"symbol": "ƒ",
"name": "Aruban Florin"
},
{
"code": "AZN",
"local": "Azerbaijan",
"symbol": "₼",
"name": "Azerbaijani Manat"
},
{
"code": "BAM",
"local": "Bosnia and Herzegovina",
"symbol": "KM",
"name": "Bosnia and Herzegovina Convertible Mark"
},
{
"code": "BBD",
"local": "Barbados",
"symbol": "Bds$",
"name": "Barbadian Dollar"
},
{
"code": "BDT",
"local": "Bangladesh",
"symbol": "৳",
"name": "Bangladeshi Taka"
},
{
"code": "BGN",
"local": "Bulgaria",
"symbol": "лв",
"name": "Bulgarian lev"
},
{
"code": "BHD",
"local": "Bahrain",
"symbol": "ب.د",
"name": "Bahraini Dinar"
},
{
"code": "BIF",
"local": "Burundi",
"symbol": "FBu",
"name": "Burundian Franc"
},
{
"code": "BMD",
"local": "Bermuda",
"symbol": "BD$",
"name": "Bermudian Dollar"
},
{
"code": "BND",
"local": "Brunei",
"symbol": "B$",
"name": "Brunei Dollar"
},
{
"code": "BOB",
"local": "Bolivia",
"symbol": "Bs.",
"name": "Bolivian Boliviano"
},
{
"code": "BRL",
"local": "Brazil",
"symbol": "R$",
"name": "Brazilian Real"
},
{
"code": "BSD",
"local": "Bahamas",
"symbol": "B$",
"name": "Bahamian Dollar"
},
{
"code": "BTN",
"local": "Bhutan",
"symbol": "Nu.",
"name": "Bhutanese Ngultrum"
},
{
"code": "BWP",
"local": "Botswana",
"symbol": "P",
"name": "Botswana Pula"
},
{
"code": "BYN",
"local": "Belarus",
"symbol": "Br",
"name": "Belarusian Ruble"
},
{
"code": "BZD",
"local": "Belize",
"symbol": "BZ$",
"name": "Belize Dollar"
},
{
"code": "CAD",
"local": "Canada",
"symbol": "C$",
"name": "Canadian Dollar"
},
{
"code": "CDF",
"local": "Democratic Republic of the Congo",
"symbol": "FC",
"name": "Congolese Franc"
},
{
"code": "CHF",
"local": "Switzerland",
"symbol": "CHF",
"name": "Swiss Franc"
},
{
"code": "CLP",
"local": "Chile",
"symbol": "CL$",
"name": "Chilean Peso"
},
{
"code": "CNY",
"local": "China",
"symbol": "¥",
"name": "Chinese Yuan"
},
{
"code": "COP",
"local": "Colombia",
"symbol": "COL$",
"name": "Colombian Peso"
},
{
"code": "CRC",
"local": "Costa Rica",
"symbol": "₡",
"name": "Costa Rican Colón"
},
{
"code": "CUP",
"local": "Cuba",
"symbol": "₱",
"name": "Cuban Peso"
},
{
"code": "CVE",
"local": "Cape Verde",
"symbol": "$",
"name": "Cape Verdean Escudo"
},
{
"code": "CZK",
"local": "Czech Republic",
"symbol": "Kč",
"name": "Czech Koruna"
},
{
"code": "DJF",
"local": "Djibouti",
"symbol": "Fdj",
"name": "Djiboutian Franc"
},
{
"code": "DKK",
"local": "Denmark",
"symbol": "kr",
"name": "Danish Krone"
},
{
"code": "DOP",
"local": "Dominican Republic",
"symbol": "RD$",
"name": "Dominican Peso"
},
{
"code": "DZD",
"local": "Algeria",
"symbol": "د.ج",
"name": "Algerian Dinar"
},
{
"code": "EGP",
"local": "Egypt",
"symbol": "£",
"name": "Egyptian Pound"
},
{
"code": "ERN",
"local": "Eritrea",
"symbol": "Nfk",
"name": "Eritrean Nakfa"
},
{
"code": "ETB",
"local": "Ethiopia",
"symbol": "Br",
"name": "Ethiopian Birr"
},
{
"code": "EUR",
"local": "Eurozone",
"symbol": "€",
"name": "Euro"
},
{
"code": "FJD",
"local": "Fiji",
"symbol": "FJ$",
"name": "Fijian Dollar"
},
{
"code": "FKP",
"local": "Falkland Islands",
"symbol": "£",
"name": "Falkland Islands Pound"
},
{
"code": "FOK",
"local": "Faroe Islands",
"symbol": "kr",
"name": "Faroese Króna"
},
{
"code": "GBP",
"local": "United Kingdom",
"symbol": "£",
"name": "British Pound Sterling"
},
{
"code": "GEL",
"local": "Georgia",
"symbol": "₾",
"name": "Georgian Lari"
},
{
"code": "GGP",
"local": "Guernsey",
"symbol": "£",
"name": "Guernsey Pound"
},
{
"code": "GHS",
"local": "Ghana",
"symbol": "GH₵",
"name": "Ghanaian Cedi"
},
{
"code": "GIP",
"local": "Gibraltar",
"symbol": "£",
"name": "Gibraltar Pound"
},
{
"code": "GMD",
"local": "Gambia",
"symbol": "D",
"name": "Gambian Dalasi"
},
{
"code": "GNF",
"local": "Guinea",
"symbol": "FG",
"name": "Guinean Franc"
},
{
"code": "GTQ",
"local": "Guatemala",
"symbol": "Q",
"name": "Guatemalan Quetzal"
},
{
"code": "GYD",
"local": "Guyana",
"symbol": "GY$",
"name": "Guyanese Dollar"
},
{
"code": "HKD",
"local": "Hong Kong",
"symbol": "HK$",
"name": "Hong Kong Dollar"
},
{
"code": "HNL",
"local": "Honduras",
"symbol": "L",
"name": "Honduran Lempira"
},
{
"code": "HRK",
"local": "Croatia",
"symbol": "kn",
"name": "Croatian Kuna"
},
{
"code": "HTG",
"local": "Haiti",
"symbol": "G",
"name": "Haitian Gourde"
},
{
"code": "HUF",
"local": "Hungary",
"symbol": "Ft",
"name": "Hungarian Forint"
},
{
"code": "IDR",
"local": "Indonesia",
"symbol": "Rp",
"name": "Indonesian Rupiah"
},
{
"code": "ILS",
"local": "Israel",
"symbol": "₪",
"name": "Israeli New Shekel"
},
{
"code": "IMP",
"local": "Isle of Man",
"symbol": "£",
"name": "Manx Pound"
},
{
"code": "INR",
"local": "India",
"symbol": "₹",
"name": "Indian Rupee"
},
{
"code": "IQD",
"local": "Iraq",
"symbol": "ع.د",
"name": "Iraqi Dinar"
},
{
"code": "IRR",
"local": "Iran",
"symbol": "﷼",
"name": "Iranian Rial"
},
{
"code": "ISK",
"local": "Iceland",
"symbol": "kr",
"name": "Icelandic Króna"
},
{
"code": "JEP",
"local": "Jersey",
"symbol": "£",
"name": "Jersey Pound"
},
{
"code": "JMD",
"local": "Jamaica",
"symbol": "J$",
"name": "Jamaican Dollar"
},
{
"code": "JOD",
"local": "Jordan",
"symbol": "د.ا",
"name": "Jordanian Dinar"
},
{
"code": "JPY",
"local": "Japan",
"symbol": "¥",
"name": "Japanese Yen"
},
{
"code": "KES",
"local": "Kenya",
"symbol": "KSh",
"name": "Kenyan Shilling"
},
{
"code": "KGS",
"local": "Kyrgyzstan",
"symbol": "с",
"name": "Kyrgyzstani Som"
},
{
"code": "KHR",
"local": "Cambodia",
"symbol": "៛",
"name": "Cambodian Riel"
},
{
"code": "KID",
"local": "Kiribati",
"symbol": "$",
"name": "Kiribati Dollar"
},
{
"code": "KMF",
"local": "Comoros",
"symbol": "CF",
"name": "Comorian Franc"
},
{
"code": "KRW",
"local": "South Korea",
"symbol": "₩",
"name": "South Korean Won"
},
{
"code": "KWD",
"local": "Kuwait",
"symbol": "د.ك",
"name": "Kuwaiti Dinar"
},
{
"code": "KYD",
"local": "Cayman Islands",
"symbol": "CI$",
"name": "Cayman Islands Dollar"
},
{
"code": "KZT",
"local": "Kazakhstan",
"symbol": "₸",
"name": "Kazakhstani Tenge"
},
{
"code": "LAK",
"local": "Laos",
"symbol": "₭",
"name": "Lao Kip"
},
{
"code": "LBP",
"local": "Lebanon",
"symbol": "ل.ل",
"name": "Lebanese Pound"
},
{
"code": "LKR",
"local": "Sri Lanka",
"symbol": "₨",
"name": "Sri Lankan Rupee"
},
{
"code": "LRD",
"local": "Liberia",
"symbol": "L$",
"name": "Liberian Dollar"
},
{
"code": "LSL",
"local": "Lesotho",
"symbol": "M",
"name": "Lesotho Loti"
},
{
"code": "LYD",
"local": "Libya",
"symbol": "ل.د",
"name": "Libyan Dinar"
},
{
"code": "MAD",
"local": "Morocco",
"symbol": "د.م.",
"name": "Moroccan Dirham"
},
{
"code": "MDL",
"local": "Moldova",
"symbol": "lei",
"name": "Moldovan Leu"
},
{
"code": "MGA",
"local": "Madagascar",
"symbol": "Ar",
"name": "Malagasy Ariary"
},
{
"code": "MKD",
"local": "North Macedonia",
"symbol": "ден",
"name": "Macedonian Denar"
},
{
"code": "MMK",
"local": "Myanmar",
"symbol": "K",
"name": "Myanmar Kyat"
},
{
"code": "MNT",
"local": "Mongolia",
"symbol": "₮",
"name": "Mongolian Tugrik"
},
{
"code": "MOP",
"local": "Macau",
"symbol": "MOP$",
"name": "Macanese Pataca"
},
{
"code": "MRU",
"local": "Mauritania",
"symbol": "UM",
"name": "Mauritanian Ouguiya"
},
{
"code": "MUR",
"local": "Mauritius",
"symbol": "₨",
"name": "Mauritian Rupee"
},
{
"code": "MVR",
"local": "Maldives",
"symbol": "Rf",
"name": "Maldivian Rufiyaa"
},
{
"code": "MWK",
"local": "Malawi",
"symbol": "MK",
"name": "Malawian Kwacha"
},
{
"code": "MXN",
"local": "Mexico",
"symbol": "Mex$",
"name": "Mexican Peso"
},
{
"code": "MYR",
"local": "Malaysia",
"symbol": "RM",
"name": "Malaysian Ringgit"
},
{
"code": "MZN",
"local": "Mozambique",
"symbol": "MT",
"name": "Mozambican Metical"
},
{
"code": "NAD",
"local": "Namibia",
"symbol": "N$",
"name": "Namibian Dollar"
},
{
"code": "NGN",
"local": "Nigeria",
"symbol": "₦",
"name": "Nigerian Naira"
},
{
"code": "NIO",
"local": "Nicaragua",
"symbol": "C$",
"name": "Nicaraguan Córdoba"
},
{
"code": "NOK",
"local": "Norway",
"symbol": "kr",
"name": "Norwegian Krone"
},
{
"code": "UAH",
"local": "Ukraine",
"symbol": "₴",
"name": "Ukrainian Hryvnia"
}
]

View File

@@ -1,7 +1,10 @@
// Package services provides the core business logic for the application.
package services
import (
"github.com/hay-kot/homebox/backend/internal/core/currencies"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/mailer"
)
type AllServices struct {
@@ -9,12 +12,14 @@ type AllServices struct {
Group *GroupService
Items *ItemService
BackgroundService *BackgroundService
Currencies *currencies.CurrencyRegistry
}
type OptionsFunc func(*options)
type options struct {
autoIncrementAssetID bool
currencies []currencies.Currency
}
func WithAutoIncrementAssetID(v bool) func(*options) {
@@ -23,13 +28,27 @@ func WithAutoIncrementAssetID(v bool) func(*options) {
}
}
func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
func WithCurrencies(v []currencies.Currency) func(*options) {
return func(o *options) {
o.currencies = v
}
}
func New(repos *repo.AllRepos, baseurl string, sender *mailer.Mailer, opts ...OptionsFunc) *AllServices {
if repos == nil {
panic("repos cannot be nil")
}
defaultCurrencies, err := currencies.CollectionCurrencies(
currencies.CollectDefaults(),
)
if err != nil {
panic("failed to collect default currencies")
}
options := &options{
autoIncrementAssetID: true,
currencies: defaultCurrencies,
}
for _, opt := range opts {
@@ -37,12 +56,17 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
}
return &AllServices{
User: &UserService{repos},
User: &UserService{
repos: repos,
mailer: sender,
baseurl: baseurl,
},
Group: &GroupService{repos},
Items: &ItemService{
repo: repos,
autoIncrementAssetID: options.autoIncrementAssetID,
},
BackgroundService: &BackgroundService{repos},
Currencies: currencies.NewCurrencyService(options.currencies),
}
}

View File

@@ -19,11 +19,11 @@ var (
type Context struct {
context.Context
// UID is a unique identifier for the acting user.
UID uuid.UUID
// UserID is a unique identifier for the acting user.
UserID uuid.UUID
// GID is a unique identifier for the acting users group.
GID uuid.UUID
// GroupID is a unique identifier for the acting users group.
GroupID uuid.UUID
// User is the acting user.
User *repo.UserOut
@@ -35,8 +35,8 @@ func NewContext(ctx context.Context) Context {
user := UseUserCtx(ctx)
return Context{
Context: ctx,
UID: user.ID,
GID: user.GroupID,
UserID: user.ID,
GroupID: user.GroupID,
User: user,
}
}

View File

@@ -6,10 +6,12 @@ import (
"os"
"testing"
"github.com/hay-kot/homebox/backend/internal/core/currencies"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/faker"
"github.com/hay-kot/homebox/backend/pkgs/mailer"
_ "github.com/mattn/go-sqlite3"
)
@@ -61,15 +63,21 @@ func TestMain(m *testing.M) {
tClient = client
tRepos = repo.New(tClient, tbus, os.TempDir()+"/homebox")
tSvc = New(tRepos)
defer client.Close()
defaults, _ := currencies.CollectionCurrencies(
currencies.CollectDefaults(),
)
tSvc = New(tRepos, "", &mailer.Mailer{}, WithCurrencies(defaults))
bootstrap()
tCtx = Context{
Context: context.Background(),
GID: tGroup.ID,
UID: tUser.ID,
GroupID: tGroup.ID,
UserID: tUser.ID,
}
os.Exit(m.Run())
exit := m.Run()
_ = client.Close()
os.Exit(exit)
}

View File

@@ -1,7 +1,8 @@
// / Package eventbus provides an interface for event bus.
// Package eventbus provides an interface for event bus.
package eventbus
import (
"context"
"sync"
"github.com/google/uuid"
@@ -34,7 +35,7 @@ type EventBus struct {
func New() *EventBus {
return &EventBus{
ch: make(chan eventData, 10),
ch: make(chan eventData, 100),
subscribers: map[Event][]func(any){
EventLabelMutation: {},
EventLocationMutation: {},
@@ -43,24 +44,29 @@ func New() *EventBus {
}
}
func (e *EventBus) Run() {
func (e *EventBus) Run(ctx context.Context) error {
if e.started {
panic("event bus already started")
}
e.started = true
for event := range e.ch {
e.mu.RLock()
arr, ok := e.subscribers[event.event]
e.mu.RUnlock()
for {
select {
case <-ctx.Done():
return nil
case event := <-e.ch:
e.mu.RLock()
arr, ok := e.subscribers[event.event]
e.mu.RUnlock()
if !ok {
continue
}
if !ok {
continue
}
for _, fn := range arr {
fn(event.data)
for _, fn := range arr {
fn(event.data)
}
}
}
}

View File

@@ -1,3 +1,4 @@
// Package reporting provides a way to import CSV files into the database.
package reporting
import (

View File

@@ -84,7 +84,7 @@ func (csf LocationString) String() string {
return strings.Join(csf, " / ")
}
func fromPathSlice(s []repo.LocationPath) LocationString {
func fromPathSlice(s []repo.ItemPath) LocationString {
v := make(LocationString, len(s))
for i := range s {

View File

@@ -152,8 +152,8 @@ func (s *IOSheet) Read(data io.Reader) error {
return nil
}
// Write writes the sheet to a writer.
func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos) error {
// ReadItems writes the sheet to a writer.
func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, groupID uuid.UUID, repos *repo.AllRepos) error {
s.Rows = make([]ExportTSVRow, len(items))
extraHeaders := map[string]struct{}{}
@@ -162,9 +162,9 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
item := items[i]
// TODO: Support fetching nested locations
locId := item.Location.ID
locID := item.Location.ID
locPaths, err := repos.Locations.PathForLoc(context.Background(), GID, locId)
locPaths, err := repos.Locations.PathForLoc(context.Background(), groupID, locID)
if err != nil {
log.Error().Err(err).Msg("could not get location path")
return err
@@ -252,7 +252,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
return nil
}
// Writes the current sheet to a writer in TSV format.
// TSV writes the current sheet to a writer in TSV format.
func (s *IOSheet) TSV() ([][]string, error) {
memcsv := make([][]string, len(s.Rows)+1)

View File

@@ -9,6 +9,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@@ -103,9 +104,9 @@ func TestSheet_Read(t *testing.T) {
switch {
case tt.wantErr:
assert.Error(t, err)
require.Error(t, err)
default:
assert.NoError(t, err)
require.NoError(t, err)
assert.ElementsMatch(t, tt.want, sheet.Rows)
}
})

View File

@@ -66,7 +66,6 @@ func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
var sendErrs []error
for i := range urls {
err := shoutrrr.Send(urls[i], bldr.String())
if err != nil {
sendErrs = append(sendErrs, err)
}

View File

@@ -21,13 +21,13 @@ func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.G
return repo.Group{}, errors.New("currency cannot be empty")
}
return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GID, data)
return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GroupID, data)
}
func (svc *GroupService) NewInvitation(ctx Context, uses int, expiresAt time.Time) (string, error) {
token := hasher.GenerateToken()
_, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GID, repo.GroupInvitationCreate{
_, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GroupID, repo.GroupInvitationCreate{
Token: token.Hash,
Uses: uses,
ExpiresAt: expiresAt,

View File

@@ -27,24 +27,24 @@ type ItemService struct {
func (svc *ItemService) Create(ctx Context, item repo.ItemCreate) (repo.ItemOut, error) {
if svc.autoIncrementAssetID {
highest, err := svc.repo.Items.GetHighestAssetID(ctx, ctx.GID)
highest, err := svc.repo.Items.GetHighestAssetID(ctx, ctx.GroupID)
if err != nil {
return repo.ItemOut{}, err
}
item.AssetID = repo.AssetID(highest + 1)
item.AssetID = highest + 1
}
return svc.repo.Items.Create(ctx, ctx.GID, item)
return svc.repo.Items.Create(ctx, ctx.GroupID, item)
}
func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int, error) {
items, err := svc.repo.Items.GetAllZeroAssetID(ctx, GID)
func (svc *ItemService) EnsureAssetID(ctx context.Context, groupID uuid.UUID) (int, error) {
items, err := svc.repo.Items.GetAllZeroAssetID(ctx, groupID)
if err != nil {
return 0, err
}
highest, err := svc.repo.Items.GetHighestAssetID(ctx, GID)
highest, err := svc.repo.Items.GetHighestAssetID(ctx, groupID)
if err != nil {
return 0, err
}
@@ -53,7 +53,7 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int,
for _, item := range items {
highest++
err = svc.repo.Items.SetAssetID(ctx, GID, item.ID, repo.AssetID(highest))
err = svc.repo.Items.SetAssetID(ctx, groupID, item.ID, highest)
if err != nil {
return 0, err
}
@@ -64,8 +64,8 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int,
return finished, nil
}
func (svc *ItemService) EnsureImportRef(ctx context.Context, GID uuid.UUID) (int, error) {
ids, err := svc.repo.Items.GetAllZeroImportRef(ctx, GID)
func (svc *ItemService) EnsureImportRef(ctx context.Context, groupID uuid.UUID) (int, error) {
ids, err := svc.repo.Items.GetAllZeroImportRef(ctx, groupID)
if err != nil {
return 0, err
}
@@ -74,7 +74,7 @@ func (svc *ItemService) EnsureImportRef(ctx context.Context, GID uuid.UUID) (int
for _, itemID := range ids {
ref := uuid.New().String()[0:8]
err = svc.repo.Items.Patch(ctx, GID, itemID, repo.ItemPatch{ImportRef: &ref})
err = svc.repo.Items.Patch(ctx, groupID, itemID, repo.ItemPatch{ImportRef: &ref})
if err != nil {
return 0, err
}
@@ -96,7 +96,7 @@ func serializeLocation[T ~[]string](location T) string {
// 1. If the item does not exist, it is created.
// 2. If the item has a ImportRef and it exists it is skipped
// 3. Locations and Labels are created if they do not exist.
func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Reader) (int, error) {
func (svc *ItemService) CsvImport(ctx context.Context, groupID uuid.UUID, data io.Reader) (int, error) {
sheet := reporting.IOSheet{}
err := sheet.Read(data)
@@ -109,7 +109,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
labelMap := make(map[string]uuid.UUID)
{
labels, err := svc.repo.Labels.GetAll(ctx, GID)
labels, err := svc.repo.Labels.GetAll(ctx, groupID)
if err != nil {
return 0, err
}
@@ -124,7 +124,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
locationMap := make(map[string]uuid.UUID)
{
locations, err := svc.repo.Locations.Tree(ctx, GID, repo.TreeQuery{WithItems: false})
locations, err := svc.repo.Locations.Tree(ctx, groupID, repo.TreeQuery{WithItems: false})
if err != nil {
return 0, err
}
@@ -153,7 +153,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
// Asset ID Pre-Check
highestAID := repo.AssetID(-1)
if svc.autoIncrementAssetID {
highestAID, err = svc.repo.Items.GetHighestAssetID(ctx, GID)
highestAID, err = svc.repo.Items.GetHighestAssetID(ctx, groupID)
if err != nil {
return 0, err
}
@@ -169,7 +169,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
// ========================================
// Preflight check for existing item
if row.ImportRef != "" {
exists, err := svc.repo.Items.CheckRef(ctx, GID, row.ImportRef)
exists, err := svc.repo.Items.CheckRef(ctx, groupID, row.ImportRef)
if err != nil {
return 0, fmt.Errorf("error checking for existing item with ref %q: %w", row.ImportRef, err)
}
@@ -188,7 +188,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
id, ok := labelMap[label]
if !ok {
newLabel, err := svc.repo.Labels.Create(ctx, GID, repo.LabelCreate{Name: label})
newLabel, err := svc.repo.Labels.Create(ctx, groupID, repo.LabelCreate{Name: label})
if err != nil {
return 0, err
}
@@ -220,7 +220,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
parentID = locationMap[parentPath]
}
newLocation, err := svc.repo.Locations.Create(ctx, GID, repo.LocationCreate{
newLocation, err := svc.repo.Locations.Create(ctx, groupID, repo.LocationCreate{
ParentID: parentID,
Name: pathElement,
})
@@ -261,12 +261,12 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
LabelIDs: labelIds,
}
item, err = svc.repo.Items.Create(ctx, GID, newItem)
item, err = svc.repo.Items.Create(ctx, groupID, newItem)
if err != nil {
return 0, err
}
default:
item, err = svc.repo.Items.GetByRef(ctx, GID, row.ImportRef)
item, err = svc.repo.Items.GetByRef(ctx, groupID, row.ImportRef)
if err != nil {
return 0, err
}
@@ -318,7 +318,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
Fields: fields,
}
item, err = svc.repo.Items.UpdateByGroup(ctx, GID, updateItem)
item, err = svc.repo.Items.UpdateByGroup(ctx, groupID, updateItem)
if err != nil {
return 0, err
}
@@ -329,15 +329,15 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
return finished, nil
}
func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]string, error) {
items, err := svc.repo.Items.GetAll(ctx, GID)
func (svc *ItemService) ExportTSV(ctx context.Context, groupID uuid.UUID) ([][]string, error) {
items, err := svc.repo.Items.GetAll(ctx, groupID)
if err != nil {
return nil, err
}
sheet := reporting.IOSheet{}
err = sheet.ReadItems(ctx, items, GID, svc.repo)
err = sheet.ReadItems(ctx, items, groupID, svc.repo)
if err != nil {
return nil, err
}
@@ -345,8 +345,8 @@ func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]strin
return sheet.TSV()
}
func (svc *ItemService) ExportBillOfMaterialsTSV(ctx context.Context, GID uuid.UUID) ([]byte, error) {
items, err := svc.repo.Items.GetAll(ctx, GID)
func (svc *ItemService) ExportBillOfMaterialsTSV(ctx context.Context, groupID uuid.UUID) ([]byte, error) {
items, err := svc.repo.Items.GetAll(ctx, groupID)
if err != nil {
return nil, err
}

View File

@@ -12,8 +12,8 @@ import (
"github.com/rs/zerolog/log"
)
func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentId uuid.UUID) (*ent.Document, error) {
attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentID uuid.UUID) (*ent.Document, error) {
attachment, err := svc.repo.Attachments.Get(ctx, attachmentID)
if err != nil {
return nil, err
}
@@ -21,7 +21,7 @@ func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentId uuid.UU
return attachment.Edges.Document, nil
}
func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) {
func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) {
// Update Attachment
attachment, err := svc.repo.Attachments.Update(ctx, data.ID, data)
if err != nil {
@@ -35,50 +35,50 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *re
return repo.ItemOut{}, err
}
return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
return svc.repo.Items.GetOneByGroup(ctx, ctx.GroupID, itemID)
}
// AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
// Table and Items table. The file provided via the reader is stored on the file system based on the provided
// relative path during construction of the service.
func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) {
func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) {
// Get the Item
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GroupID, itemID)
if err != nil {
return repo.ItemOut{}, err
}
// Create the document
doc, err := svc.repo.Docs.Create(ctx, ctx.GID, repo.DocumentCreate{Title: filename, Content: file})
doc, err := svc.repo.Docs.Create(ctx, ctx.GroupID, repo.DocumentCreate{Title: filename, Content: file})
if err != nil {
log.Err(err).Msg("failed to create document")
return repo.ItemOut{}, err
}
// Create the attachment
_, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachmentType)
_, err = svc.repo.Attachments.Create(ctx, itemID, doc.ID, attachmentType)
if err != nil {
log.Err(err).Msg("failed to create attachment")
return repo.ItemOut{}, err
}
return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
return svc.repo.Items.GetOneByGroup(ctx, ctx.GroupID, itemID)
}
func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemId, attachmentId uuid.UUID) error {
func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemID, attachmentID uuid.UUID) error {
// Get the Item
_, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemId)
_, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemID)
if err != nil {
return err
}
attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
attachment, err := svc.repo.Attachments.Get(ctx, attachmentID)
if err != nil {
return err
}
// Delete the attachment
err = svc.repo.Attachments.Delete(ctx, attachmentId)
err = svc.repo.Attachments.Delete(ctx, attachmentID)
if err != nil {
return err
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestItemService_AddAttachment(t *testing.T) {
@@ -23,7 +24,7 @@ func TestItemService_AddAttachment(t *testing.T) {
Description: "test",
Name: "test",
})
assert.NoError(t, err)
require.NoError(t, err)
assert.NotNil(t, loc)
itmC := repo.ItemCreate{
@@ -33,11 +34,11 @@ func TestItemService_AddAttachment(t *testing.T) {
}
itm, err := svc.repo.Items.Create(context.Background(), tGroup.ID, itmC)
assert.NoError(t, err)
require.NoError(t, err)
assert.NotNil(t, itm)
t.Cleanup(func() {
err := svc.repo.Items.Delete(context.Background(), itm.ID)
assert.NoError(t, err)
require.NoError(t, err)
})
contents := fk.Str(1000)
@@ -45,7 +46,7 @@ func TestItemService_AddAttachment(t *testing.T) {
// Setup
afterAttachment, err := svc.AttachmentAdd(tCtx, itm.ID, "testfile.txt", "attachment", reader)
assert.NoError(t, err)
require.NoError(t, err)
assert.NotNil(t, afterAttachment)
// Check that the file exists
@@ -56,6 +57,6 @@ func TestItemService_AddAttachment(t *testing.T) {
// Check that the file contents are correct
bts, err := os.ReadFile(storedPath)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, contents, string(bts))
}

View File

@@ -3,12 +3,15 @@ package services
import (
"context"
"errors"
"net/url"
"time"
"github.com/google/uuid"
"github.com/hay-kot/easyemails"
"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/hasher"
"github.com/hay-kot/homebox/backend/pkgs/mailer"
"github.com/rs/zerolog/log"
)
@@ -16,11 +19,18 @@ var (
oneWeek = time.Hour * 24 * 7
ErrorInvalidLogin = errors.New("invalid username or password")
ErrorInvalidToken = errors.New("invalid token")
ErrorTokenIdMismatch = errors.New("token id mismatch")
ErrorTokenIDMismatch = errors.New("token id mismatch")
)
func init() { // nolint: gochecknoinits
easyemails.ImageLogoHeader = "https://raw.githubusercontent.com/hay-kot/homebox/af9aa239af66df17478f5ed9283e303daf7c6775/docs/docs/assets/img/homebox-email-banner.jpg"
easyemails.ColorPrimary = "#5D7F67"
}
type UserService struct {
repos *repo.AllRepos
repos *repo.AllRepos
mailer *mailer.Mailer
baseurl string
}
type (
@@ -39,6 +49,9 @@ type (
Username string `json:"username"`
Password string `json:"password"`
}
PasswordResetRequest struct {
Email string `json:"email"`
}
)
// RegisterUser creates a new user and group in the data with the provided data. It also bootstraps the user's group
@@ -92,9 +105,11 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
if err != nil {
return repo.UserOut{}, err
}
log.Debug().Msg("user created")
// Create the default labels and locations for the group.
if creatingGroup {
log.Debug().Msg("creating default labels")
for _, label := range defaultLabels() {
_, err := svc.repos.Labels.Create(ctx, usr.GroupID, label)
if err != nil {
@@ -102,6 +117,7 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
}
}
log.Debug().Msg("creating default locations")
for _, location := range defaultLocations() {
_, err := svc.repos.Locations.Create(ctx, usr.GroupID, location)
if err != nil {
@@ -112,6 +128,7 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
// Decrement the invitation token if it was used.
if token.ID != uuid.Nil {
log.Debug().Msg("decrementing invitation token")
err = svc.repos.Groups.InvitationUpdate(ctx, token.ID, token.Uses-1)
if err != nil {
log.Err(err).Msg("Failed to update invitation token")
@@ -128,19 +145,19 @@ func (svc *UserService) GetSelf(ctx context.Context, requestToken string) (repo.
return svc.repos.AuthTokens.GetUserFromToken(ctx, hash)
}
func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) {
err := svc.repos.Users.Update(ctx, ID, data)
func (svc *UserService) UpdateSelf(ctx context.Context, userID uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) {
err := svc.repos.Users.Update(ctx, userID, data)
if err != nil {
return repo.UserOut{}, err
}
return svc.repos.Users.GetOneId(ctx, ID)
return svc.repos.Users.GetOneID(ctx, userID)
}
// ============================================================================
// User Authentication
func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID, extendedSession bool) (UserAuthTokenDetail, error) {
func (svc *UserService) createSessionToken(ctx context.Context, userID uuid.UUID, extendedSession bool) (UserAuthTokenDetail, error) {
attachmentToken := hasher.GenerateToken()
expiresAt := time.Now().Add(oneWeek)
@@ -149,7 +166,7 @@ func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID
}
attachmentData := repo.UserAuthTokenCreate{
UserID: userId,
UserID: userID,
TokenHash: attachmentToken.Hash,
ExpiresAt: expiresAt,
}
@@ -161,7 +178,7 @@ func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID
userToken := hasher.GenerateToken()
data := repo.UserAuthTokenCreate{
UserID: userId,
UserID: userID,
TokenHash: userToken.Hash,
ExpiresAt: expiresAt,
}
@@ -213,28 +230,28 @@ func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthT
// DeleteSelf deletes the user that is currently logged based of the provided UUID
// There is _NO_ protection against deleting the wrong user, as such this should only
// be used when the identify of the user has been confirmed.
func (svc *UserService) DeleteSelf(ctx context.Context, ID uuid.UUID) error {
return svc.repos.Users.Delete(ctx, ID)
func (svc *UserService) DeleteSelf(ctx context.Context, userID uuid.UUID) error {
return svc.repos.Users.Delete(ctx, userID)
}
func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) {
usr, err := svc.repos.Users.GetOneId(ctx, ctx.UID)
func (svc *UserService) PasswordChange(ctx Context, currentPassword, newPassword string) (ok bool) {
usr, err := svc.repos.Users.GetOneID(ctx, ctx.UserID)
if err != nil {
return false
}
if !hasher.CheckPasswordHash(current, usr.PasswordHash) {
if !hasher.CheckPasswordHash(currentPassword, usr.PasswordHash) {
log.Err(errors.New("current password is incorrect")).Msg("Failed to change password")
return false
}
hashed, err := hasher.HashPassword(new)
hashed, err := hasher.HashPassword(newPassword)
if err != nil {
log.Err(err).Msg("Failed to hash password")
return false
}
err = svc.repos.Users.ChangePassword(ctx.Context, ctx.UID, hashed)
err = svc.repos.Users.ChangePassword(ctx.Context, ctx.UserID, hashed)
if err != nil {
log.Err(err).Msg("Failed to change password")
return false
@@ -242,3 +259,80 @@ func (svc *UserService) ChangePassword(ctx Context, current string, new string)
return true
}
func (svc *UserService) PasswordChangeWithToken(ctx Context, token, newPassword string) error {
hashed, err := hasher.HashPassword(newPassword)
if err != nil {
return err
}
tokenHash := hasher.HashToken(token)
resetToken, err := svc.repos.Users.PasswordResetGet(ctx.Context, tokenHash)
if err != nil {
return err
}
if resetToken.UserID != ctx.UserID {
return ErrorTokenIDMismatch
}
err = svc.repos.Users.ChangePassword(ctx.Context, ctx.UserID, hashed)
if err != nil {
return err
}
err = svc.repos.Users.PasswordResetDelete(ctx.Context, tokenHash)
if err != nil {
return err
}
return nil
}
func (svc *UserService) PasswordResetRequest(ctx context.Context, req PasswordResetRequest) error {
usr, err := svc.repos.Users.GetOneEmail(ctx, req.Email)
if err != nil {
log.Warn().Err(err).Msg("failed to get user for email reset")
return err
}
token := hasher.GenerateToken()
err = svc.repos.Users.PasswordResetCreate(ctx, usr.ID, token.Hash)
if err != nil {
return err
}
resetURL, err := url.JoinPath(svc.baseurl, "reset-password/")
if err != nil {
return err
}
resetURL = resetURL + "?token=" + token.Raw
bldr := easyemails.NewBuilder().Add(
easyemails.WithParagraph(
easyemails.WithText("You have requested a password reset. Please click the link below to reset your password."),
),
easyemails.WithButton("Reset Password", resetURL),
easyemails.WithParagraph(
easyemails.WithText("[Github](https://github.com/hay-kot/homebox) · [Docs](https://hay-kot.github.io/homebox/)").
Centered(),
).
FontSize(12),
)
msg := mailer.NewMessageBuilder().
SetBody(bldr.Render()).
SetSubject("Password Reset").
SetTo(usr.Name, usr.Email).
Build()
err = svc.mailer.Send(msg)
if err != nil {
log.Err(err).Msg("Failed to send password reset email")
return err
}
return nil
}

View File

@@ -0,0 +1,184 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
// ActionToken is the model entity for the ActionToken schema.
type ActionToken struct {
config `json:"-"`
// ID of the ent.
ID uuid.UUID `json:"id,omitempty"`
// UserID holds the value of the "user_id" field.
UserID uuid.UUID `json:"user_id,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// Action holds the value of the "action" field.
Action actiontoken.Action `json:"action,omitempty"`
// Token holds the value of the "token" field.
Token []byte `json:"token,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ActionTokenQuery when eager-loading is set.
Edges ActionTokenEdges `json:"edges"`
selectValues sql.SelectValues
}
// ActionTokenEdges holds the relations/edges for other nodes in the graph.
type ActionTokenEdges struct {
// User holds the value of the user edge.
User *User `json:"user,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// UserOrErr returns the User value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e ActionTokenEdges) UserOrErr() (*User, error) {
if e.loadedTypes[0] {
if e.User == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: user.Label}
}
return e.User, nil
}
return nil, &NotLoadedError{edge: "user"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*ActionToken) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case actiontoken.FieldToken:
values[i] = new([]byte)
case actiontoken.FieldAction:
values[i] = new(sql.NullString)
case actiontoken.FieldCreatedAt, actiontoken.FieldUpdatedAt:
values[i] = new(sql.NullTime)
case actiontoken.FieldID, actiontoken.FieldUserID:
values[i] = new(uuid.UUID)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the ActionToken fields.
func (at *ActionToken) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case actiontoken.FieldID:
if value, ok := values[i].(*uuid.UUID); !ok {
return fmt.Errorf("unexpected type %T for field id", values[i])
} else if value != nil {
at.ID = *value
}
case actiontoken.FieldUserID:
if value, ok := values[i].(*uuid.UUID); !ok {
return fmt.Errorf("unexpected type %T for field user_id", values[i])
} else if value != nil {
at.UserID = *value
}
case actiontoken.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
at.CreatedAt = value.Time
}
case actiontoken.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
at.UpdatedAt = value.Time
}
case actiontoken.FieldAction:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field action", values[i])
} else if value.Valid {
at.Action = actiontoken.Action(value.String)
}
case actiontoken.FieldToken:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field token", values[i])
} else if value != nil {
at.Token = *value
}
default:
at.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the ActionToken.
// This includes values selected through modifiers, order, etc.
func (at *ActionToken) Value(name string) (ent.Value, error) {
return at.selectValues.Get(name)
}
// QueryUser queries the "user" edge of the ActionToken entity.
func (at *ActionToken) QueryUser() *UserQuery {
return NewActionTokenClient(at.config).QueryUser(at)
}
// Update returns a builder for updating this ActionToken.
// Note that you need to call ActionToken.Unwrap() before calling this method if this ActionToken
// was returned from a transaction, and the transaction was committed or rolled back.
func (at *ActionToken) Update() *ActionTokenUpdateOne {
return NewActionTokenClient(at.config).UpdateOne(at)
}
// Unwrap unwraps the ActionToken entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (at *ActionToken) Unwrap() *ActionToken {
_tx, ok := at.config.driver.(*txDriver)
if !ok {
panic("ent: ActionToken is not a transactional entity")
}
at.config.driver = _tx.drv
return at
}
// String implements the fmt.Stringer.
func (at *ActionToken) String() string {
var builder strings.Builder
builder.WriteString("ActionToken(")
builder.WriteString(fmt.Sprintf("id=%v, ", at.ID))
builder.WriteString("user_id=")
builder.WriteString(fmt.Sprintf("%v", at.UserID))
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(at.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(at.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("action=")
builder.WriteString(fmt.Sprintf("%v", at.Action))
builder.WriteString(", ")
builder.WriteString("token=")
builder.WriteString(fmt.Sprintf("%v", at.Token))
builder.WriteByte(')')
return builder.String()
}
// ActionTokens is a parsable slice of ActionToken.
type ActionTokens []*ActionToken

View File

@@ -0,0 +1,138 @@
// Code generated by ent, DO NOT EDIT.
package actiontoken
import (
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
)
const (
// Label holds the string label denoting the actiontoken type in the database.
Label = "action_token"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldAction holds the string denoting the action field in the database.
FieldAction = "action"
// FieldToken holds the string denoting the token field in the database.
FieldToken = "token"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// Table holds the table name of the actiontoken in the database.
Table = "action_tokens"
// UserTable is the table that holds the user relation/edge.
UserTable = "action_tokens"
// UserInverseTable is the table name for the User entity.
// It exists in this package in order to avoid circular dependency with the "user" package.
UserInverseTable = "users"
// UserColumn is the table column denoting the user relation/edge.
UserColumn = "user_id"
)
// Columns holds all SQL columns for actiontoken fields.
var Columns = []string{
FieldID,
FieldUserID,
FieldCreatedAt,
FieldUpdatedAt,
FieldAction,
FieldToken,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
// DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID
)
// Action defines the type for the "action" enum field.
type Action string
// ActionResetPassword is the default value of the Action enum.
const DefaultAction = ActionResetPassword
// Action values.
const (
ActionResetPassword Action = "reset_password"
)
func (a Action) String() string {
return string(a)
}
// ActionValidator is a validator for the "action" field enum values. It is called by the builders before save.
func ActionValidator(a Action) error {
switch a {
case ActionResetPassword:
return nil
default:
return fmt.Errorf("actiontoken: invalid enum value for action field: %q", a)
}
}
// OrderOption defines the ordering options for the ActionToken queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByAction orders the results by the action field.
func ByAction(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAction, opts...).ToFunc()
}
// ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
}
}
func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
}

View File

@@ -0,0 +1,275 @@
// Code generated by ent, DO NOT EDIT.
package actiontoken
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLTE(FieldID, id))
}
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
func UserID(v uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldUserID, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldUpdatedAt, v))
}
// Token applies equality check predicate on the "token" field. It's identical to TokenEQ.
func Token(v []byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldToken, v))
}
// UserIDEQ applies the EQ predicate on the "user_id" field.
func UserIDEQ(v uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldUserID, v))
}
// UserIDNEQ applies the NEQ predicate on the "user_id" field.
func UserIDNEQ(v uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNEQ(FieldUserID, v))
}
// UserIDIn applies the In predicate on the "user_id" field.
func UserIDIn(vs ...uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldIn(FieldUserID, vs...))
}
// UserIDNotIn applies the NotIn predicate on the "user_id" field.
func UserIDNotIn(vs ...uuid.UUID) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNotIn(FieldUserID, vs...))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLTE(FieldUpdatedAt, v))
}
// ActionEQ applies the EQ predicate on the "action" field.
func ActionEQ(v Action) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldAction, v))
}
// ActionNEQ applies the NEQ predicate on the "action" field.
func ActionNEQ(v Action) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNEQ(FieldAction, v))
}
// ActionIn applies the In predicate on the "action" field.
func ActionIn(vs ...Action) predicate.ActionToken {
return predicate.ActionToken(sql.FieldIn(FieldAction, vs...))
}
// ActionNotIn applies the NotIn predicate on the "action" field.
func ActionNotIn(vs ...Action) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNotIn(FieldAction, vs...))
}
// TokenEQ applies the EQ predicate on the "token" field.
func TokenEQ(v []byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldEQ(FieldToken, v))
}
// TokenNEQ applies the NEQ predicate on the "token" field.
func TokenNEQ(v []byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNEQ(FieldToken, v))
}
// TokenIn applies the In predicate on the "token" field.
func TokenIn(vs ...[]byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldIn(FieldToken, vs...))
}
// TokenNotIn applies the NotIn predicate on the "token" field.
func TokenNotIn(vs ...[]byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldNotIn(FieldToken, vs...))
}
// TokenGT applies the GT predicate on the "token" field.
func TokenGT(v []byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGT(FieldToken, v))
}
// TokenGTE applies the GTE predicate on the "token" field.
func TokenGTE(v []byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldGTE(FieldToken, v))
}
// TokenLT applies the LT predicate on the "token" field.
func TokenLT(v []byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLT(FieldToken, v))
}
// TokenLTE applies the LTE predicate on the "token" field.
func TokenLTE(v []byte) predicate.ActionToken {
return predicate.ActionToken(sql.FieldLTE(FieldToken, v))
}
// HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.ActionToken {
return predicate.ActionToken(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
func HasUserWith(preds ...predicate.User) predicate.ActionToken {
return predicate.ActionToken(func(s *sql.Selector) {
step := newUserStep()
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.ActionToken) predicate.ActionToken {
return predicate.ActionToken(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.ActionToken) predicate.ActionToken {
return predicate.ActionToken(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.ActionToken) predicate.ActionToken {
return predicate.ActionToken(sql.NotPredicates(p))
}

View File

@@ -0,0 +1,329 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
// ActionTokenCreate is the builder for creating a ActionToken entity.
type ActionTokenCreate struct {
config
mutation *ActionTokenMutation
hooks []Hook
}
// SetUserID sets the "user_id" field.
func (atc *ActionTokenCreate) SetUserID(u uuid.UUID) *ActionTokenCreate {
atc.mutation.SetUserID(u)
return atc
}
// SetCreatedAt sets the "created_at" field.
func (atc *ActionTokenCreate) SetCreatedAt(t time.Time) *ActionTokenCreate {
atc.mutation.SetCreatedAt(t)
return atc
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (atc *ActionTokenCreate) SetNillableCreatedAt(t *time.Time) *ActionTokenCreate {
if t != nil {
atc.SetCreatedAt(*t)
}
return atc
}
// SetUpdatedAt sets the "updated_at" field.
func (atc *ActionTokenCreate) SetUpdatedAt(t time.Time) *ActionTokenCreate {
atc.mutation.SetUpdatedAt(t)
return atc
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (atc *ActionTokenCreate) SetNillableUpdatedAt(t *time.Time) *ActionTokenCreate {
if t != nil {
atc.SetUpdatedAt(*t)
}
return atc
}
// SetAction sets the "action" field.
func (atc *ActionTokenCreate) SetAction(a actiontoken.Action) *ActionTokenCreate {
atc.mutation.SetAction(a)
return atc
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (atc *ActionTokenCreate) SetNillableAction(a *actiontoken.Action) *ActionTokenCreate {
if a != nil {
atc.SetAction(*a)
}
return atc
}
// SetToken sets the "token" field.
func (atc *ActionTokenCreate) SetToken(b []byte) *ActionTokenCreate {
atc.mutation.SetToken(b)
return atc
}
// SetID sets the "id" field.
func (atc *ActionTokenCreate) SetID(u uuid.UUID) *ActionTokenCreate {
atc.mutation.SetID(u)
return atc
}
// SetNillableID sets the "id" field if the given value is not nil.
func (atc *ActionTokenCreate) SetNillableID(u *uuid.UUID) *ActionTokenCreate {
if u != nil {
atc.SetID(*u)
}
return atc
}
// SetUser sets the "user" edge to the User entity.
func (atc *ActionTokenCreate) SetUser(u *User) *ActionTokenCreate {
return atc.SetUserID(u.ID)
}
// Mutation returns the ActionTokenMutation object of the builder.
func (atc *ActionTokenCreate) Mutation() *ActionTokenMutation {
return atc.mutation
}
// Save creates the ActionToken in the database.
func (atc *ActionTokenCreate) Save(ctx context.Context) (*ActionToken, error) {
atc.defaults()
return withHooks(ctx, atc.sqlSave, atc.mutation, atc.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (atc *ActionTokenCreate) SaveX(ctx context.Context) *ActionToken {
v, err := atc.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (atc *ActionTokenCreate) Exec(ctx context.Context) error {
_, err := atc.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (atc *ActionTokenCreate) ExecX(ctx context.Context) {
if err := atc.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (atc *ActionTokenCreate) defaults() {
if _, ok := atc.mutation.CreatedAt(); !ok {
v := actiontoken.DefaultCreatedAt()
atc.mutation.SetCreatedAt(v)
}
if _, ok := atc.mutation.UpdatedAt(); !ok {
v := actiontoken.DefaultUpdatedAt()
atc.mutation.SetUpdatedAt(v)
}
if _, ok := atc.mutation.Action(); !ok {
v := actiontoken.DefaultAction
atc.mutation.SetAction(v)
}
if _, ok := atc.mutation.ID(); !ok {
v := actiontoken.DefaultID()
atc.mutation.SetID(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (atc *ActionTokenCreate) check() error {
if _, ok := atc.mutation.UserID(); !ok {
return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "ActionToken.user_id"`)}
}
if _, ok := atc.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "ActionToken.created_at"`)}
}
if _, ok := atc.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "ActionToken.updated_at"`)}
}
if _, ok := atc.mutation.Action(); !ok {
return &ValidationError{Name: "action", err: errors.New(`ent: missing required field "ActionToken.action"`)}
}
if v, ok := atc.mutation.Action(); ok {
if err := actiontoken.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "ActionToken.action": %w`, err)}
}
}
if _, ok := atc.mutation.Token(); !ok {
return &ValidationError{Name: "token", err: errors.New(`ent: missing required field "ActionToken.token"`)}
}
if _, ok := atc.mutation.UserID(); !ok {
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "ActionToken.user"`)}
}
return nil
}
func (atc *ActionTokenCreate) sqlSave(ctx context.Context) (*ActionToken, error) {
if err := atc.check(); err != nil {
return nil, err
}
_node, _spec := atc.createSpec()
if err := sqlgraph.CreateNode(ctx, atc.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
if _spec.ID.Value != nil {
if id, ok := _spec.ID.Value.(*uuid.UUID); ok {
_node.ID = *id
} else if err := _node.ID.Scan(_spec.ID.Value); err != nil {
return nil, err
}
}
atc.mutation.id = &_node.ID
atc.mutation.done = true
return _node, nil
}
func (atc *ActionTokenCreate) createSpec() (*ActionToken, *sqlgraph.CreateSpec) {
var (
_node = &ActionToken{config: atc.config}
_spec = sqlgraph.NewCreateSpec(actiontoken.Table, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID))
)
if id, ok := atc.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = &id
}
if value, ok := atc.mutation.CreatedAt(); ok {
_spec.SetField(actiontoken.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if value, ok := atc.mutation.UpdatedAt(); ok {
_spec.SetField(actiontoken.FieldUpdatedAt, field.TypeTime, value)
_node.UpdatedAt = value
}
if value, ok := atc.mutation.Action(); ok {
_spec.SetField(actiontoken.FieldAction, field.TypeEnum, value)
_node.Action = value
}
if value, ok := atc.mutation.Token(); ok {
_spec.SetField(actiontoken.FieldToken, field.TypeBytes, value)
_node.Token = value
}
if nodes := atc.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: actiontoken.UserTable,
Columns: []string{actiontoken.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.UserID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// ActionTokenCreateBulk is the builder for creating many ActionToken entities in bulk.
type ActionTokenCreateBulk struct {
config
err error
builders []*ActionTokenCreate
}
// Save creates the ActionToken entities in the database.
func (atcb *ActionTokenCreateBulk) Save(ctx context.Context) ([]*ActionToken, error) {
if atcb.err != nil {
return nil, atcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(atcb.builders))
nodes := make([]*ActionToken, len(atcb.builders))
mutators := make([]Mutator, len(atcb.builders))
for i := range atcb.builders {
func(i int, root context.Context) {
builder := atcb.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*ActionTokenMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, atcb.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, atcb.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, atcb.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (atcb *ActionTokenCreateBulk) SaveX(ctx context.Context) []*ActionToken {
v, err := atcb.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (atcb *ActionTokenCreateBulk) Exec(ctx context.Context) error {
_, err := atcb.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (atcb *ActionTokenCreateBulk) ExecX(ctx context.Context) {
if err := atcb.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
)
// ActionTokenDelete is the builder for deleting a ActionToken entity.
type ActionTokenDelete struct {
config
hooks []Hook
mutation *ActionTokenMutation
}
// Where appends a list predicates to the ActionTokenDelete builder.
func (atd *ActionTokenDelete) Where(ps ...predicate.ActionToken) *ActionTokenDelete {
atd.mutation.Where(ps...)
return atd
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (atd *ActionTokenDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, atd.sqlExec, atd.mutation, atd.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (atd *ActionTokenDelete) ExecX(ctx context.Context) int {
n, err := atd.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (atd *ActionTokenDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(actiontoken.Table, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID))
if ps := atd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, atd.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
atd.mutation.done = true
return affected, err
}
// ActionTokenDeleteOne is the builder for deleting a single ActionToken entity.
type ActionTokenDeleteOne struct {
atd *ActionTokenDelete
}
// Where appends a list predicates to the ActionTokenDelete builder.
func (atdo *ActionTokenDeleteOne) Where(ps ...predicate.ActionToken) *ActionTokenDeleteOne {
atdo.atd.mutation.Where(ps...)
return atdo
}
// Exec executes the deletion query.
func (atdo *ActionTokenDeleteOne) Exec(ctx context.Context) error {
n, err := atdo.atd.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{actiontoken.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (atdo *ActionTokenDeleteOne) ExecX(ctx context.Context) {
if err := atdo.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,606 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
// ActionTokenQuery is the builder for querying ActionToken entities.
type ActionTokenQuery struct {
config
ctx *QueryContext
order []actiontoken.OrderOption
inters []Interceptor
predicates []predicate.ActionToken
withUser *UserQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the ActionTokenQuery builder.
func (atq *ActionTokenQuery) Where(ps ...predicate.ActionToken) *ActionTokenQuery {
atq.predicates = append(atq.predicates, ps...)
return atq
}
// Limit the number of records to be returned by this query.
func (atq *ActionTokenQuery) Limit(limit int) *ActionTokenQuery {
atq.ctx.Limit = &limit
return atq
}
// Offset to start from.
func (atq *ActionTokenQuery) Offset(offset int) *ActionTokenQuery {
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 *ActionTokenQuery) Unique(unique bool) *ActionTokenQuery {
atq.ctx.Unique = &unique
return atq
}
// Order specifies how the records should be ordered.
func (atq *ActionTokenQuery) Order(o ...actiontoken.OrderOption) *ActionTokenQuery {
atq.order = append(atq.order, o...)
return atq
}
// QueryUser chains the current query on the "user" edge.
func (atq *ActionTokenQuery) QueryUser() *UserQuery {
query := (&UserClient{config: atq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := atq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := atq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(actiontoken.Table, actiontoken.FieldID, selector),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, actiontoken.UserTable, actiontoken.UserColumn),
)
fromU = sqlgraph.SetNeighbors(atq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first ActionToken entity from the query.
// Returns a *NotFoundError when no ActionToken was found.
func (atq *ActionTokenQuery) First(ctx context.Context) (*ActionToken, error) {
nodes, err := atq.Limit(1).All(setContextOp(ctx, atq.ctx, "First"))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{actiontoken.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (atq *ActionTokenQuery) FirstX(ctx context.Context) *ActionToken {
node, err := atq.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first ActionToken ID from the query.
// Returns a *NotFoundError when no ActionToken ID was found.
func (atq *ActionTokenQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = atq.Limit(1).IDs(setContextOp(ctx, atq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{actiontoken.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (atq *ActionTokenQuery) FirstIDX(ctx context.Context) uuid.UUID {
id, err := atq.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single ActionToken entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one ActionToken entity is found.
// Returns a *NotFoundError when no ActionToken entities are found.
func (atq *ActionTokenQuery) Only(ctx context.Context) (*ActionToken, error) {
nodes, err := atq.Limit(2).All(setContextOp(ctx, atq.ctx, "Only"))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{actiontoken.Label}
default:
return nil, &NotSingularError{actiontoken.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (atq *ActionTokenQuery) OnlyX(ctx context.Context) *ActionToken {
node, err := atq.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only ActionToken ID in the query.
// Returns a *NotSingularError when more than one ActionToken ID is found.
// Returns a *NotFoundError when no entities are found.
func (atq *ActionTokenQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = atq.Limit(2).IDs(setContextOp(ctx, atq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{actiontoken.Label}
default:
err = &NotSingularError{actiontoken.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (atq *ActionTokenQuery) OnlyIDX(ctx context.Context) uuid.UUID {
id, err := atq.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of ActionTokens.
func (atq *ActionTokenQuery) All(ctx context.Context) ([]*ActionToken, error) {
ctx = setContextOp(ctx, atq.ctx, "All")
if err := atq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*ActionToken, *ActionTokenQuery]()
return withInterceptors[[]*ActionToken](ctx, atq, qr, atq.inters)
}
// AllX is like All, but panics if an error occurs.
func (atq *ActionTokenQuery) AllX(ctx context.Context) []*ActionToken {
nodes, err := atq.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of ActionToken IDs.
func (atq *ActionTokenQuery) 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(actiontoken.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (atq *ActionTokenQuery) IDsX(ctx context.Context) []uuid.UUID {
ids, err := atq.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (atq *ActionTokenQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, atq.ctx, "Count")
if err := atq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, atq, querierCount[*ActionTokenQuery](), atq.inters)
}
// CountX is like Count, but panics if an error occurs.
func (atq *ActionTokenQuery) CountX(ctx context.Context) int {
count, err := atq.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (atq *ActionTokenQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, atq.ctx, "Exist")
switch _, err := atq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (atq *ActionTokenQuery) ExistX(ctx context.Context) bool {
exist, err := atq.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the ActionTokenQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (atq *ActionTokenQuery) Clone() *ActionTokenQuery {
if atq == nil {
return nil
}
return &ActionTokenQuery{
config: atq.config,
ctx: atq.ctx.Clone(),
order: append([]actiontoken.OrderOption{}, atq.order...),
inters: append([]Interceptor{}, atq.inters...),
predicates: append([]predicate.ActionToken{}, atq.predicates...),
withUser: atq.withUser.Clone(),
// clone intermediate query.
sql: atq.sql.Clone(),
path: atq.path,
}
}
// WithUser tells the query-builder to eager-load the nodes that are connected to
// the "user" edge. The optional arguments are used to configure the query builder of the edge.
func (atq *ActionTokenQuery) WithUser(opts ...func(*UserQuery)) *ActionTokenQuery {
query := (&UserClient{config: atq.config}).Query()
for _, opt := range opts {
opt(query)
}
atq.withUser = query
return atq
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// UserID uuid.UUID `json:"user_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.ActionToken.Query().
// GroupBy(actiontoken.FieldUserID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (atq *ActionTokenQuery) GroupBy(field string, fields ...string) *ActionTokenGroupBy {
atq.ctx.Fields = append([]string{field}, fields...)
grbuild := &ActionTokenGroupBy{build: atq}
grbuild.flds = &atq.ctx.Fields
grbuild.label = actiontoken.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// UserID uuid.UUID `json:"user_id,omitempty"`
// }
//
// client.ActionToken.Query().
// Select(actiontoken.FieldUserID).
// Scan(ctx, &v)
func (atq *ActionTokenQuery) Select(fields ...string) *ActionTokenSelect {
atq.ctx.Fields = append(atq.ctx.Fields, fields...)
sbuild := &ActionTokenSelect{ActionTokenQuery: atq}
sbuild.label = actiontoken.Label
sbuild.flds, sbuild.scan = &atq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a ActionTokenSelect configured with the given aggregations.
func (atq *ActionTokenQuery) Aggregate(fns ...AggregateFunc) *ActionTokenSelect {
return atq.Select().Aggregate(fns...)
}
func (atq *ActionTokenQuery) prepareQuery(ctx context.Context) error {
for _, inter := range atq.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, atq); err != nil {
return err
}
}
}
for _, f := range atq.ctx.Fields {
if !actiontoken.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if atq.path != nil {
prev, err := atq.path(ctx)
if err != nil {
return err
}
atq.sql = prev
}
return nil
}
func (atq *ActionTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ActionToken, error) {
var (
nodes = []*ActionToken{}
_spec = atq.querySpec()
loadedTypes = [1]bool{
atq.withUser != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*ActionToken).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &ActionToken{config: atq.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, atq.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := atq.withUser; query != nil {
if err := atq.loadUser(ctx, query, nodes, nil,
func(n *ActionToken, e *User) { n.Edges.User = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (atq *ActionTokenQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*ActionToken, init func(*ActionToken), assign func(*ActionToken, *User)) error {
ids := make([]uuid.UUID, 0, len(nodes))
nodeids := make(map[uuid.UUID][]*ActionToken)
for i := range nodes {
fk := nodes[i].UserID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
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 {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (atq *ActionTokenQuery) sqlCount(ctx context.Context) (int, error) {
_spec := atq.querySpec()
_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 *ActionTokenQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(actiontoken.Table, actiontoken.Columns, sqlgraph.NewFieldSpec(actiontoken.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.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, actiontoken.FieldID)
for i := range fields {
if fields[i] != actiontoken.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if atq.withUser != nil {
_spec.Node.AddColumnOnce(actiontoken.FieldUserID)
}
}
if ps := atq.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := atq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := atq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := atq.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (atq *ActionTokenQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(atq.driver.Dialect())
t1 := builder.Table(actiontoken.Table)
columns := atq.ctx.Fields
if len(columns) == 0 {
columns = actiontoken.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if atq.sql != nil {
selector = atq.sql
selector.Select(selector.Columns(columns...)...)
}
if atq.ctx.Unique != nil && *atq.ctx.Unique {
selector.Distinct()
}
for _, p := range atq.predicates {
p(selector)
}
for _, p := range atq.order {
p(selector)
}
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.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ActionTokenGroupBy is the group-by builder for ActionToken entities.
type ActionTokenGroupBy struct {
selector
build *ActionTokenQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (atgb *ActionTokenGroupBy) Aggregate(fns ...AggregateFunc) *ActionTokenGroupBy {
atgb.fns = append(atgb.fns, fns...)
return atgb
}
// Scan applies the selector query and scans the result into the given value.
func (atgb *ActionTokenGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, atgb.build.ctx, "GroupBy")
if err := atgb.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*ActionTokenQuery, *ActionTokenGroupBy](ctx, atgb.build, atgb, atgb.build.inters, v)
}
func (atgb *ActionTokenGroupBy) sqlScan(ctx context.Context, root *ActionTokenQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(atgb.fns))
for _, fn := range atgb.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*atgb.flds)+len(atgb.fns))
for _, f := range *atgb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*atgb.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := atgb.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// ActionTokenSelect is the builder for selecting fields of ActionToken entities.
type ActionTokenSelect struct {
*ActionTokenQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (ats *ActionTokenSelect) Aggregate(fns ...AggregateFunc) *ActionTokenSelect {
ats.fns = append(ats.fns, fns...)
return ats
}
// Scan applies the selector query and scans the result into the given value.
func (ats *ActionTokenSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, ats.ctx, "Select")
if err := ats.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*ActionTokenQuery, *ActionTokenSelect](ctx, ats.ActionTokenQuery, ats, ats.inters, v)
}
func (ats *ActionTokenSelect) sqlScan(ctx context.Context, root *ActionTokenQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(ats.fns))
for _, fn := range ats.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*ats.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := ats.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@@ -0,0 +1,406 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
)
// ActionTokenUpdate is the builder for updating ActionToken entities.
type ActionTokenUpdate struct {
config
hooks []Hook
mutation *ActionTokenMutation
}
// Where appends a list predicates to the ActionTokenUpdate builder.
func (atu *ActionTokenUpdate) Where(ps ...predicate.ActionToken) *ActionTokenUpdate {
atu.mutation.Where(ps...)
return atu
}
// SetUserID sets the "user_id" field.
func (atu *ActionTokenUpdate) SetUserID(u uuid.UUID) *ActionTokenUpdate {
atu.mutation.SetUserID(u)
return atu
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (atu *ActionTokenUpdate) SetNillableUserID(u *uuid.UUID) *ActionTokenUpdate {
if u != nil {
atu.SetUserID(*u)
}
return atu
}
// SetUpdatedAt sets the "updated_at" field.
func (atu *ActionTokenUpdate) SetUpdatedAt(t time.Time) *ActionTokenUpdate {
atu.mutation.SetUpdatedAt(t)
return atu
}
// SetAction sets the "action" field.
func (atu *ActionTokenUpdate) SetAction(a actiontoken.Action) *ActionTokenUpdate {
atu.mutation.SetAction(a)
return atu
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (atu *ActionTokenUpdate) SetNillableAction(a *actiontoken.Action) *ActionTokenUpdate {
if a != nil {
atu.SetAction(*a)
}
return atu
}
// SetToken sets the "token" field.
func (atu *ActionTokenUpdate) SetToken(b []byte) *ActionTokenUpdate {
atu.mutation.SetToken(b)
return atu
}
// SetUser sets the "user" edge to the User entity.
func (atu *ActionTokenUpdate) SetUser(u *User) *ActionTokenUpdate {
return atu.SetUserID(u.ID)
}
// Mutation returns the ActionTokenMutation object of the builder.
func (atu *ActionTokenUpdate) Mutation() *ActionTokenMutation {
return atu.mutation
}
// ClearUser clears the "user" edge to the User entity.
func (atu *ActionTokenUpdate) ClearUser() *ActionTokenUpdate {
atu.mutation.ClearUser()
return atu
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (atu *ActionTokenUpdate) Save(ctx context.Context) (int, error) {
atu.defaults()
return withHooks(ctx, atu.sqlSave, atu.mutation, atu.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (atu *ActionTokenUpdate) SaveX(ctx context.Context) int {
affected, err := atu.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (atu *ActionTokenUpdate) Exec(ctx context.Context) error {
_, err := atu.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (atu *ActionTokenUpdate) ExecX(ctx context.Context) {
if err := atu.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (atu *ActionTokenUpdate) defaults() {
if _, ok := atu.mutation.UpdatedAt(); !ok {
v := actiontoken.UpdateDefaultUpdatedAt()
atu.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (atu *ActionTokenUpdate) check() error {
if v, ok := atu.mutation.Action(); ok {
if err := actiontoken.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "ActionToken.action": %w`, err)}
}
}
if _, ok := atu.mutation.UserID(); atu.mutation.UserCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "ActionToken.user"`)
}
return nil
}
func (atu *ActionTokenUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := atu.check(); err != nil {
return n, err
}
_spec := sqlgraph.NewUpdateSpec(actiontoken.Table, actiontoken.Columns, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID))
if ps := atu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := atu.mutation.UpdatedAt(); ok {
_spec.SetField(actiontoken.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := atu.mutation.Action(); ok {
_spec.SetField(actiontoken.FieldAction, field.TypeEnum, value)
}
if value, ok := atu.mutation.Token(); ok {
_spec.SetField(actiontoken.FieldToken, field.TypeBytes, value)
}
if atu.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: actiontoken.UserTable,
Columns: []string{actiontoken.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := atu.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: actiontoken.UserTable,
Columns: []string{actiontoken.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if n, err = sqlgraph.UpdateNodes(ctx, atu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{actiontoken.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
atu.mutation.done = true
return n, nil
}
// ActionTokenUpdateOne is the builder for updating a single ActionToken entity.
type ActionTokenUpdateOne struct {
config
fields []string
hooks []Hook
mutation *ActionTokenMutation
}
// SetUserID sets the "user_id" field.
func (atuo *ActionTokenUpdateOne) SetUserID(u uuid.UUID) *ActionTokenUpdateOne {
atuo.mutation.SetUserID(u)
return atuo
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (atuo *ActionTokenUpdateOne) SetNillableUserID(u *uuid.UUID) *ActionTokenUpdateOne {
if u != nil {
atuo.SetUserID(*u)
}
return atuo
}
// SetUpdatedAt sets the "updated_at" field.
func (atuo *ActionTokenUpdateOne) SetUpdatedAt(t time.Time) *ActionTokenUpdateOne {
atuo.mutation.SetUpdatedAt(t)
return atuo
}
// SetAction sets the "action" field.
func (atuo *ActionTokenUpdateOne) SetAction(a actiontoken.Action) *ActionTokenUpdateOne {
atuo.mutation.SetAction(a)
return atuo
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (atuo *ActionTokenUpdateOne) SetNillableAction(a *actiontoken.Action) *ActionTokenUpdateOne {
if a != nil {
atuo.SetAction(*a)
}
return atuo
}
// SetToken sets the "token" field.
func (atuo *ActionTokenUpdateOne) SetToken(b []byte) *ActionTokenUpdateOne {
atuo.mutation.SetToken(b)
return atuo
}
// SetUser sets the "user" edge to the User entity.
func (atuo *ActionTokenUpdateOne) SetUser(u *User) *ActionTokenUpdateOne {
return atuo.SetUserID(u.ID)
}
// Mutation returns the ActionTokenMutation object of the builder.
func (atuo *ActionTokenUpdateOne) Mutation() *ActionTokenMutation {
return atuo.mutation
}
// ClearUser clears the "user" edge to the User entity.
func (atuo *ActionTokenUpdateOne) ClearUser() *ActionTokenUpdateOne {
atuo.mutation.ClearUser()
return atuo
}
// Where appends a list predicates to the ActionTokenUpdate builder.
func (atuo *ActionTokenUpdateOne) Where(ps ...predicate.ActionToken) *ActionTokenUpdateOne {
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 *ActionTokenUpdateOne) Select(field string, fields ...string) *ActionTokenUpdateOne {
atuo.fields = append([]string{field}, fields...)
return atuo
}
// Save executes the query and returns the updated ActionToken entity.
func (atuo *ActionTokenUpdateOne) Save(ctx context.Context) (*ActionToken, error) {
atuo.defaults()
return withHooks(ctx, atuo.sqlSave, atuo.mutation, atuo.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (atuo *ActionTokenUpdateOne) SaveX(ctx context.Context) *ActionToken {
node, err := atuo.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (atuo *ActionTokenUpdateOne) Exec(ctx context.Context) error {
_, err := atuo.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (atuo *ActionTokenUpdateOne) ExecX(ctx context.Context) {
if err := atuo.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (atuo *ActionTokenUpdateOne) defaults() {
if _, ok := atuo.mutation.UpdatedAt(); !ok {
v := actiontoken.UpdateDefaultUpdatedAt()
atuo.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (atuo *ActionTokenUpdateOne) check() error {
if v, ok := atuo.mutation.Action(); ok {
if err := actiontoken.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "ActionToken.action": %w`, err)}
}
}
if _, ok := atuo.mutation.UserID(); atuo.mutation.UserCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "ActionToken.user"`)
}
return nil
}
func (atuo *ActionTokenUpdateOne) sqlSave(ctx context.Context) (_node *ActionToken, err error) {
if err := atuo.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(actiontoken.Table, actiontoken.Columns, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID))
id, ok := atuo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ActionToken.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := atuo.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, actiontoken.FieldID)
for _, f := range fields {
if !actiontoken.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != actiontoken.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := atuo.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := atuo.mutation.UpdatedAt(); ok {
_spec.SetField(actiontoken.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := atuo.mutation.Action(); ok {
_spec.SetField(actiontoken.FieldAction, field.TypeEnum, value)
}
if value, ok := atuo.mutation.Token(); ok {
_spec.SetField(actiontoken.FieldToken, field.TypeBytes, value)
}
if atuo.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: actiontoken.UserTable,
Columns: []string{actiontoken.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := atuo.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: actiontoken.UserTable,
Columns: []string{actiontoken.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &ActionToken{config: atuo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, atuo.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{actiontoken.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
atuo.mutation.done = true
return _node, nil
}

View File

@@ -16,6 +16,7 @@ import (
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"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"
@@ -36,6 +37,8 @@ type Client struct {
config
// Schema is the client for creating, migrating and dropping schema.
Schema *migrate.Schema
// ActionToken is the client for interacting with the ActionToken builders.
ActionToken *ActionTokenClient
// Attachment is the client for interacting with the Attachment builders.
Attachment *AttachmentClient
// AuthRoles is the client for interacting with the AuthRoles builders.
@@ -73,6 +76,7 @@ func NewClient(opts ...Option) *Client {
func (c *Client) init() {
c.Schema = migrate.NewSchema(c.driver)
c.ActionToken = NewActionTokenClient(c.config)
c.Attachment = NewAttachmentClient(c.config)
c.AuthRoles = NewAuthRolesClient(c.config)
c.AuthTokens = NewAuthTokensClient(c.config)
@@ -178,6 +182,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
return &Tx{
ctx: ctx,
config: cfg,
ActionToken: NewActionTokenClient(cfg),
Attachment: NewAttachmentClient(cfg),
AuthRoles: NewAuthRolesClient(cfg),
AuthTokens: NewAuthTokensClient(cfg),
@@ -210,6 +215,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
return &Tx{
ctx: ctx,
config: cfg,
ActionToken: NewActionTokenClient(cfg),
Attachment: NewAttachmentClient(cfg),
AuthRoles: NewAuthRolesClient(cfg),
AuthTokens: NewAuthTokensClient(cfg),
@@ -229,7 +235,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
// Debug returns a new debug-client. It's used to get verbose logging on specific operations.
//
// client.Debug().
// Attachment.
// ActionToken.
// Query().
// Count(ctx)
func (c *Client) Debug() *Client {
@@ -252,7 +258,7 @@ func (c *Client) Close() error {
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group,
c.ActionToken, 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,
} {
@@ -264,7 +270,7 @@ func (c *Client) Use(hooks ...Hook) {
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group,
c.ActionToken, 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,
} {
@@ -275,6 +281,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
// Mutate implements the ent.Mutator interface.
func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
switch m := m.(type) {
case *ActionTokenMutation:
return c.ActionToken.mutate(ctx, m)
case *AttachmentMutation:
return c.Attachment.mutate(ctx, m)
case *AuthRolesMutation:
@@ -306,6 +314,155 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
}
}
// ActionTokenClient is a client for the ActionToken schema.
type ActionTokenClient struct {
config
}
// NewActionTokenClient returns a client for the ActionToken from the given config.
func NewActionTokenClient(c config) *ActionTokenClient {
return &ActionTokenClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `actiontoken.Hooks(f(g(h())))`.
func (c *ActionTokenClient) Use(hooks ...Hook) {
c.hooks.ActionToken = append(c.hooks.ActionToken, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `actiontoken.Intercept(f(g(h())))`.
func (c *ActionTokenClient) Intercept(interceptors ...Interceptor) {
c.inters.ActionToken = append(c.inters.ActionToken, interceptors...)
}
// Create returns a builder for creating a ActionToken entity.
func (c *ActionTokenClient) Create() *ActionTokenCreate {
mutation := newActionTokenMutation(c.config, OpCreate)
return &ActionTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of ActionToken entities.
func (c *ActionTokenClient) CreateBulk(builders ...*ActionTokenCreate) *ActionTokenCreateBulk {
return &ActionTokenCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *ActionTokenClient) MapCreateBulk(slice any, setFunc func(*ActionTokenCreate, int)) *ActionTokenCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &ActionTokenCreateBulk{err: fmt.Errorf("calling to ActionTokenClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*ActionTokenCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &ActionTokenCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for ActionToken.
func (c *ActionTokenClient) Update() *ActionTokenUpdate {
mutation := newActionTokenMutation(c.config, OpUpdate)
return &ActionTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *ActionTokenClient) UpdateOne(at *ActionToken) *ActionTokenUpdateOne {
mutation := newActionTokenMutation(c.config, OpUpdateOne, withActionToken(at))
return &ActionTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *ActionTokenClient) UpdateOneID(id uuid.UUID) *ActionTokenUpdateOne {
mutation := newActionTokenMutation(c.config, OpUpdateOne, withActionTokenID(id))
return &ActionTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for ActionToken.
func (c *ActionTokenClient) Delete() *ActionTokenDelete {
mutation := newActionTokenMutation(c.config, OpDelete)
return &ActionTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *ActionTokenClient) DeleteOne(at *ActionToken) *ActionTokenDeleteOne {
return c.DeleteOneID(at.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *ActionTokenClient) DeleteOneID(id uuid.UUID) *ActionTokenDeleteOne {
builder := c.Delete().Where(actiontoken.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &ActionTokenDeleteOne{builder}
}
// Query returns a query builder for ActionToken.
func (c *ActionTokenClient) Query() *ActionTokenQuery {
return &ActionTokenQuery{
config: c.config,
ctx: &QueryContext{Type: TypeActionToken},
inters: c.Interceptors(),
}
}
// Get returns a ActionToken entity by its id.
func (c *ActionTokenClient) Get(ctx context.Context, id uuid.UUID) (*ActionToken, error) {
return c.Query().Where(actiontoken.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *ActionTokenClient) GetX(ctx context.Context, id uuid.UUID) *ActionToken {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryUser queries the user edge of a ActionToken.
func (c *ActionTokenClient) QueryUser(at *ActionToken) *UserQuery {
query := (&UserClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := at.ID
step := sqlgraph.NewStep(
sqlgraph.From(actiontoken.Table, actiontoken.FieldID, id),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, actiontoken.UserTable, actiontoken.UserColumn),
)
fromV = sqlgraph.Neighbors(at.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *ActionTokenClient) Hooks() []Hook {
return c.hooks.ActionToken
}
// Interceptors returns the client interceptors.
func (c *ActionTokenClient) Interceptors() []Interceptor {
return c.inters.ActionToken
}
func (c *ActionTokenClient) mutate(ctx context.Context, m *ActionTokenMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&ActionTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&ActionTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&ActionTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&ActionTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown ActionToken mutation op: %q", m.Op())
}
}
// AttachmentClient is a client for the Attachment schema.
type AttachmentClient struct {
config
@@ -2586,6 +2743,22 @@ func (c *UserClient) QueryNotifiers(u *User) *NotifierQuery {
return query
}
// QueryActionTokens queries the action_tokens edge of a User.
func (c *UserClient) QueryActionTokens(u *User) *ActionTokenQuery {
query := (&ActionTokenClient{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(actiontoken.Table, actiontoken.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.ActionTokensTable, user.ActionTokensColumn),
)
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
@@ -2614,11 +2787,13 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error)
// 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
ActionToken, 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
ActionToken, Attachment, AuthRoles, AuthTokens, Document, Group,
GroupInvitationToken, Item, ItemField, Label, Location, MaintenanceEntry,
Notifier, User []ent.Interceptor
}
)

View File

@@ -12,6 +12,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"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"
@@ -85,6 +86,7 @@ var (
func checkColumn(table, column string) error {
initCheck.Do(func() {
columnCheck = sql.NewColumnCheck(map[string]func(string) bool{
actiontoken.Table: actiontoken.ValidColumn,
attachment.Table: attachment.ValidColumn,
authroles.Table: authroles.ValidColumn,
authtokens.Table: authtokens.ValidColumn,

View File

@@ -25,7 +25,7 @@ type Group struct {
// Name holds the value of the "name" field.
Name string `json:"name,omitempty"`
// Currency holds the value of the "currency" field.
Currency group.Currency `json:"currency,omitempty"`
Currency string `json:"currency,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the GroupQuery when eager-loading is set.
Edges GroupEdges `json:"edges"`
@@ -170,7 +170,7 @@ func (gr *Group) assignValues(columns []string, values []any) error {
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field currency", values[i])
} else if value.Valid {
gr.Currency = group.Currency(value.String)
gr.Currency = value.String
}
default:
gr.selectValues.Set(columns[i], values[i])
@@ -253,7 +253,7 @@ func (gr *Group) String() string {
builder.WriteString(gr.Name)
builder.WriteString(", ")
builder.WriteString("currency=")
builder.WriteString(fmt.Sprintf("%v", gr.Currency))
builder.WriteString(gr.Currency)
builder.WriteByte(')')
return builder.String()
}

View File

@@ -3,7 +3,6 @@
package group
import (
"fmt"
"time"
"entgo.io/ent/dialect/sql"
@@ -119,65 +118,12 @@ var (
UpdateDefaultUpdatedAt func() time.Time
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DefaultCurrency holds the default value on creation for the "currency" field.
DefaultCurrency string
// DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID
)
// Currency defines the type for the "currency" enum field.
type Currency string
// CurrencyUsd is the default value of the Currency enum.
const DefaultCurrency = CurrencyUsd
// Currency values.
const (
CurrencyAed Currency = "aed"
CurrencyAud Currency = "aud"
CurrencyBgn Currency = "bgn"
CurrencyBrl Currency = "brl"
CurrencyCad Currency = "cad"
CurrencyChf Currency = "chf"
CurrencyCzk Currency = "czk"
CurrencyDkk Currency = "dkk"
CurrencyEur Currency = "eur"
CurrencyGbp Currency = "gbp"
CurrencyHkd Currency = "hkd"
CurrencyIdr Currency = "idr"
CurrencyInr Currency = "inr"
CurrencyJpy Currency = "jpy"
CurrencyKrw Currency = "krw"
CurrencyMxn Currency = "mxn"
CurrencyNok Currency = "nok"
CurrencyNzd Currency = "nzd"
CurrencyPln Currency = "pln"
CurrencyRmb Currency = "rmb"
CurrencyRon Currency = "ron"
CurrencyRub Currency = "rub"
CurrencySar Currency = "sar"
CurrencySek Currency = "sek"
CurrencySgd Currency = "sgd"
CurrencyThb Currency = "thb"
CurrencyTry Currency = "try"
CurrencyUsd Currency = "usd"
CurrencyXag Currency = "xag"
CurrencyXau Currency = "xau"
CurrencyZar Currency = "zar"
)
func (c Currency) String() string {
return string(c)
}
// 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 CurrencyAed, CurrencyAud, CurrencyBgn, CurrencyBrl, CurrencyCad, CurrencyChf, CurrencyCzk, CurrencyDkk, CurrencyEur, CurrencyGbp, CurrencyHkd, CurrencyIdr, CurrencyInr, CurrencyJpy, CurrencyKrw, CurrencyMxn, CurrencyNok, CurrencyNzd, CurrencyPln, CurrencyRmb, CurrencyRon, CurrencyRub, CurrencySar, CurrencySek, CurrencySgd, CurrencyThb, CurrencyTry, CurrencyUsd, CurrencyXag, CurrencyXau, CurrencyZar:
return nil
default:
return fmt.Errorf("group: invalid enum value for currency field: %q", c)
}
}
// OrderOption defines the ordering options for the Group queries.
type OrderOption func(*sql.Selector)

View File

@@ -71,6 +71,11 @@ func Name(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldName, v))
}
// Currency applies equality check predicate on the "currency" field. It's identical to CurrencyEQ.
func Currency(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldCurrency, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldCreatedAt, v))
@@ -217,25 +222,70 @@ func NameContainsFold(v string) predicate.Group {
}
// CurrencyEQ applies the EQ predicate on the "currency" field.
func CurrencyEQ(v Currency) predicate.Group {
func CurrencyEQ(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldCurrency, v))
}
// CurrencyNEQ applies the NEQ predicate on the "currency" field.
func CurrencyNEQ(v Currency) predicate.Group {
func CurrencyNEQ(v string) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldCurrency, v))
}
// CurrencyIn applies the In predicate on the "currency" field.
func CurrencyIn(vs ...Currency) predicate.Group {
func CurrencyIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldIn(FieldCurrency, vs...))
}
// CurrencyNotIn applies the NotIn predicate on the "currency" field.
func CurrencyNotIn(vs ...Currency) predicate.Group {
func CurrencyNotIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldCurrency, vs...))
}
// CurrencyGT applies the GT predicate on the "currency" field.
func CurrencyGT(v string) predicate.Group {
return predicate.Group(sql.FieldGT(FieldCurrency, v))
}
// CurrencyGTE applies the GTE predicate on the "currency" field.
func CurrencyGTE(v string) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldCurrency, v))
}
// CurrencyLT applies the LT predicate on the "currency" field.
func CurrencyLT(v string) predicate.Group {
return predicate.Group(sql.FieldLT(FieldCurrency, v))
}
// CurrencyLTE applies the LTE predicate on the "currency" field.
func CurrencyLTE(v string) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldCurrency, v))
}
// CurrencyContains applies the Contains predicate on the "currency" field.
func CurrencyContains(v string) predicate.Group {
return predicate.Group(sql.FieldContains(FieldCurrency, v))
}
// CurrencyHasPrefix applies the HasPrefix predicate on the "currency" field.
func CurrencyHasPrefix(v string) predicate.Group {
return predicate.Group(sql.FieldHasPrefix(FieldCurrency, v))
}
// CurrencyHasSuffix applies the HasSuffix predicate on the "currency" field.
func CurrencyHasSuffix(v string) predicate.Group {
return predicate.Group(sql.FieldHasSuffix(FieldCurrency, v))
}
// CurrencyEqualFold applies the EqualFold predicate on the "currency" field.
func CurrencyEqualFold(v string) predicate.Group {
return predicate.Group(sql.FieldEqualFold(FieldCurrency, v))
}
// CurrencyContainsFold applies the ContainsFold predicate on the "currency" field.
func CurrencyContainsFold(v string) predicate.Group {
return predicate.Group(sql.FieldContainsFold(FieldCurrency, v))
}
// HasUsers applies the HasEdge predicate on the "users" edge.
func HasUsers() predicate.Group {
return predicate.Group(func(s *sql.Selector) {

View File

@@ -63,15 +63,15 @@ func (gc *GroupCreate) SetName(s string) *GroupCreate {
}
// SetCurrency sets the "currency" field.
func (gc *GroupCreate) SetCurrency(gr group.Currency) *GroupCreate {
gc.mutation.SetCurrency(gr)
func (gc *GroupCreate) SetCurrency(s string) *GroupCreate {
gc.mutation.SetCurrency(s)
return gc
}
// SetNillableCurrency sets the "currency" field if the given value is not nil.
func (gc *GroupCreate) SetNillableCurrency(gr *group.Currency) *GroupCreate {
if gr != nil {
gc.SetCurrency(*gr)
func (gc *GroupCreate) SetNillableCurrency(s *string) *GroupCreate {
if s != nil {
gc.SetCurrency(*s)
}
return gc
}
@@ -267,11 +267,6 @@ func (gc *GroupCreate) check() error {
if _, ok := gc.mutation.Currency(); !ok {
return &ValidationError{Name: "currency", err: errors.New(`ent: missing required field "Group.currency"`)}
}
if v, ok := gc.mutation.Currency(); ok {
if err := group.CurrencyValidator(v); err != nil {
return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "Group.currency": %w`, err)}
}
}
return nil
}
@@ -320,7 +315,7 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
_node.Name = value
}
if value, ok := gc.mutation.Currency(); ok {
_spec.SetField(group.FieldCurrency, field.TypeEnum, value)
_spec.SetField(group.FieldCurrency, field.TypeString, value)
_node.Currency = value
}
if nodes := gc.mutation.UsersIDs(); len(nodes) > 0 {

View File

@@ -57,15 +57,15 @@ func (gu *GroupUpdate) SetNillableName(s *string) *GroupUpdate {
}
// SetCurrency sets the "currency" field.
func (gu *GroupUpdate) SetCurrency(gr group.Currency) *GroupUpdate {
gu.mutation.SetCurrency(gr)
func (gu *GroupUpdate) SetCurrency(s string) *GroupUpdate {
gu.mutation.SetCurrency(s)
return gu
}
// SetNillableCurrency sets the "currency" field if the given value is not nil.
func (gu *GroupUpdate) SetNillableCurrency(gr *group.Currency) *GroupUpdate {
if gr != nil {
gu.SetCurrency(*gr)
func (gu *GroupUpdate) SetNillableCurrency(s *string) *GroupUpdate {
if s != nil {
gu.SetCurrency(*s)
}
return gu
}
@@ -370,11 +370,6 @@ func (gu *GroupUpdate) check() error {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Group.name": %w`, err)}
}
}
if v, ok := gu.mutation.Currency(); ok {
if err := group.CurrencyValidator(v); err != nil {
return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "Group.currency": %w`, err)}
}
}
return nil
}
@@ -397,7 +392,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) {
_spec.SetField(group.FieldName, field.TypeString, value)
}
if value, ok := gu.mutation.Currency(); ok {
_spec.SetField(group.FieldCurrency, field.TypeEnum, value)
_spec.SetField(group.FieldCurrency, field.TypeString, value)
}
if gu.mutation.UsersCleared() {
edge := &sqlgraph.EdgeSpec{
@@ -755,15 +750,15 @@ func (guo *GroupUpdateOne) SetNillableName(s *string) *GroupUpdateOne {
}
// SetCurrency sets the "currency" field.
func (guo *GroupUpdateOne) SetCurrency(gr group.Currency) *GroupUpdateOne {
guo.mutation.SetCurrency(gr)
func (guo *GroupUpdateOne) SetCurrency(s string) *GroupUpdateOne {
guo.mutation.SetCurrency(s)
return guo
}
// SetNillableCurrency sets the "currency" field if the given value is not nil.
func (guo *GroupUpdateOne) SetNillableCurrency(gr *group.Currency) *GroupUpdateOne {
if gr != nil {
guo.SetCurrency(*gr)
func (guo *GroupUpdateOne) SetNillableCurrency(s *string) *GroupUpdateOne {
if s != nil {
guo.SetCurrency(*s)
}
return guo
}
@@ -1081,11 +1076,6 @@ func (guo *GroupUpdateOne) check() error {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Group.name": %w`, err)}
}
}
if v, ok := guo.mutation.Currency(); ok {
if err := group.CurrencyValidator(v); err != nil {
return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "Group.currency": %w`, err)}
}
}
return nil
}
@@ -1125,7 +1115,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error
_spec.SetField(group.FieldName, field.TypeString, value)
}
if value, ok := guo.mutation.Currency(); ok {
_spec.SetField(group.FieldCurrency, field.TypeEnum, value)
_spec.SetField(group.FieldCurrency, field.TypeString, value)
}
if guo.mutation.UsersCleared() {
edge := &sqlgraph.EdgeSpec{

View File

@@ -4,6 +4,10 @@ package ent
import "github.com/google/uuid"
func (at *ActionToken) GetID() uuid.UUID {
return at.ID
}
func (a *Attachment) GetID() uuid.UUID {
return a.ID
}

View File

@@ -9,6 +9,18 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent"
)
// The ActionTokenFunc type is an adapter to allow the use of ordinary
// function as ActionToken mutator.
type ActionTokenFunc func(context.Context, *ent.ActionTokenMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f ActionTokenFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.ActionTokenMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ActionTokenMutation", m)
}
// The AttachmentFunc type is an adapter to allow the use of ordinary
// function as Attachment mutator.
type AttachmentFunc func(context.Context, *ent.AttachmentMutation) (ent.Value, error)

View File

@@ -8,6 +8,46 @@ import (
)
var (
// ActionTokensColumns holds the columns for the "action_tokens" table.
ActionTokensColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
{Name: "action", Type: field.TypeEnum, Enums: []string{"reset_password"}, Default: "reset_password"},
{Name: "token", Type: field.TypeBytes, Unique: true},
{Name: "user_id", Type: field.TypeUUID},
}
// ActionTokensTable holds the schema information for the "action_tokens" table.
ActionTokensTable = &schema.Table{
Name: "action_tokens",
Columns: ActionTokensColumns,
PrimaryKey: []*schema.Column{ActionTokensColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "action_tokens_users_action_tokens",
Columns: []*schema.Column{ActionTokensColumns[5]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.Cascade,
},
},
Indexes: []*schema.Index{
{
Name: "actiontoken_token",
Unique: false,
Columns: []*schema.Column{ActionTokensColumns[4]},
},
{
Name: "actiontoken_action",
Unique: false,
Columns: []*schema.Column{ActionTokensColumns[3]},
},
{
Name: "actiontoken_user_id",
Unique: false,
Columns: []*schema.Column{ActionTokensColumns[5]},
},
},
}
// AttachmentsColumns holds the columns for the "attachments" table.
AttachmentsColumns = []*schema.Column{
{Name: "id", Type: field.TypeUUID},
@@ -117,7 +157,7 @@ var (
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
{Name: "name", Type: field.TypeString, Size: 255},
{Name: "currency", Type: field.TypeEnum, Enums: []string{"aed", "aud", "bgn", "brl", "cad", "chf", "czk", "dkk", "eur", "gbp", "hkd", "idr", "inr", "jpy", "krw", "mxn", "nok", "nzd", "pln", "rmb", "ron", "rub", "sar", "sek", "sgd", "thb", "try", "usd", "xag", "xau", "zar"}, Default: "usd"},
{Name: "currency", Type: field.TypeString, Default: "usd"},
}
// GroupsTable holds the schema information for the "groups" table.
GroupsTable = &schema.Table{
@@ -453,6 +493,7 @@ var (
}
// Tables holds all the tables in the schema.
Tables = []*schema.Table{
ActionTokensTable,
AttachmentsTable,
AuthRolesTable,
AuthTokensTable,
@@ -471,6 +512,7 @@ var (
)
func init() {
ActionTokensTable.ForeignKeys[0].RefTable = UsersTable
AttachmentsTable.ForeignKeys[0].RefTable = DocumentsTable
AttachmentsTable.ForeignKeys[1].RefTable = ItemsTable
AuthRolesTable.ForeignKeys[0].RefTable = AuthTokensTable

View File

@@ -12,6 +12,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"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"
@@ -37,6 +38,7 @@ const (
OpUpdateOne = ent.OpUpdateOne
// Node types.
TypeActionToken = "ActionToken"
TypeAttachment = "Attachment"
TypeAuthRoles = "AuthRoles"
TypeAuthTokens = "AuthTokens"
@@ -52,6 +54,608 @@ const (
TypeUser = "User"
)
// ActionTokenMutation represents an operation that mutates the ActionToken nodes in the graph.
type ActionTokenMutation struct {
config
op Op
typ string
id *uuid.UUID
created_at *time.Time
updated_at *time.Time
action *actiontoken.Action
token *[]byte
clearedFields map[string]struct{}
user *uuid.UUID
cleareduser bool
done bool
oldValue func(context.Context) (*ActionToken, error)
predicates []predicate.ActionToken
}
var _ ent.Mutation = (*ActionTokenMutation)(nil)
// actiontokenOption allows management of the mutation configuration using functional options.
type actiontokenOption func(*ActionTokenMutation)
// newActionTokenMutation creates new mutation for the ActionToken entity.
func newActionTokenMutation(c config, op Op, opts ...actiontokenOption) *ActionTokenMutation {
m := &ActionTokenMutation{
config: c,
op: op,
typ: TypeActionToken,
clearedFields: make(map[string]struct{}),
}
for _, opt := range opts {
opt(m)
}
return m
}
// withActionTokenID sets the ID field of the mutation.
func withActionTokenID(id uuid.UUID) actiontokenOption {
return func(m *ActionTokenMutation) {
var (
err error
once sync.Once
value *ActionToken
)
m.oldValue = func(ctx context.Context) (*ActionToken, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
value, err = m.Client().ActionToken.Get(ctx, id)
}
})
return value, err
}
m.id = &id
}
}
// withActionToken sets the old ActionToken of the mutation.
func withActionToken(node *ActionToken) actiontokenOption {
return func(m *ActionTokenMutation) {
m.oldValue = func(context.Context) (*ActionToken, error) {
return node, nil
}
m.id = &node.ID
}
}
// Client returns a new `ent.Client` from the mutation. If the mutation was
// executed in a transaction (ent.Tx), a transactional client is returned.
func (m ActionTokenMutation) Client() *Client {
client := &Client{config: m.config}
client.init()
return client
}
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
// it returns an error otherwise.
func (m ActionTokenMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
tx := &Tx{config: m.config}
tx.init()
return tx, nil
}
// SetID sets the value of the id field. Note that this
// operation is only accepted on creation of ActionToken entities.
func (m *ActionTokenMutation) SetID(id uuid.UUID) {
m.id = &id
}
// ID returns the ID value in the mutation. Note that the ID is only available
// if it was provided to the builder or after it was returned from the database.
func (m *ActionTokenMutation) ID() (id uuid.UUID, exists bool) {
if m.id == nil {
return
}
return *m.id, true
}
// IDs queries the database and returns the entity ids that match the mutation's predicate.
// That means, if the mutation is applied within a transaction with an isolation level such
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
// or updated by the mutation.
func (m *ActionTokenMutation) IDs(ctx context.Context) ([]uuid.UUID, error) {
switch {
case m.op.Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
if exists {
return []uuid.UUID{id}, nil
}
fallthrough
case m.op.Is(OpUpdate | OpDelete):
return m.Client().ActionToken.Query().Where(m.predicates...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
}
}
// SetUserID sets the "user_id" field.
func (m *ActionTokenMutation) SetUserID(u uuid.UUID) {
m.user = &u
}
// UserID returns the value of the "user_id" field in the mutation.
func (m *ActionTokenMutation) UserID() (r uuid.UUID, exists bool) {
v := m.user
if v == nil {
return
}
return *v, true
}
// OldUserID returns the old "user_id" field's value of the ActionToken entity.
// If the ActionToken object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ActionTokenMutation) OldUserID(ctx context.Context) (v uuid.UUID, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUserID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldUserID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldUserID: %w", err)
}
return oldValue.UserID, nil
}
// ResetUserID resets all changes to the "user_id" field.
func (m *ActionTokenMutation) ResetUserID() {
m.user = nil
}
// SetCreatedAt sets the "created_at" field.
func (m *ActionTokenMutation) SetCreatedAt(t time.Time) {
m.created_at = &t
}
// CreatedAt returns the value of the "created_at" field in the mutation.
func (m *ActionTokenMutation) CreatedAt() (r time.Time, exists bool) {
v := m.created_at
if v == nil {
return
}
return *v, true
}
// OldCreatedAt returns the old "created_at" field's value of the ActionToken entity.
// If the ActionToken object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ActionTokenMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldCreatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
}
return oldValue.CreatedAt, nil
}
// ResetCreatedAt resets all changes to the "created_at" field.
func (m *ActionTokenMutation) ResetCreatedAt() {
m.created_at = nil
}
// SetUpdatedAt sets the "updated_at" field.
func (m *ActionTokenMutation) SetUpdatedAt(t time.Time) {
m.updated_at = &t
}
// UpdatedAt returns the value of the "updated_at" field in the mutation.
func (m *ActionTokenMutation) UpdatedAt() (r time.Time, exists bool) {
v := m.updated_at
if v == nil {
return
}
return *v, true
}
// OldUpdatedAt returns the old "updated_at" field's value of the ActionToken entity.
// If the ActionToken object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ActionTokenMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
}
return oldValue.UpdatedAt, nil
}
// ResetUpdatedAt resets all changes to the "updated_at" field.
func (m *ActionTokenMutation) ResetUpdatedAt() {
m.updated_at = nil
}
// SetAction sets the "action" field.
func (m *ActionTokenMutation) SetAction(a actiontoken.Action) {
m.action = &a
}
// Action returns the value of the "action" field in the mutation.
func (m *ActionTokenMutation) Action() (r actiontoken.Action, exists bool) {
v := m.action
if v == nil {
return
}
return *v, true
}
// OldAction returns the old "action" field's value of the ActionToken entity.
// If the ActionToken object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ActionTokenMutation) OldAction(ctx context.Context) (v actiontoken.Action, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldAction is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldAction requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldAction: %w", err)
}
return oldValue.Action, nil
}
// ResetAction resets all changes to the "action" field.
func (m *ActionTokenMutation) ResetAction() {
m.action = nil
}
// SetToken sets the "token" field.
func (m *ActionTokenMutation) SetToken(b []byte) {
m.token = &b
}
// Token returns the value of the "token" field in the mutation.
func (m *ActionTokenMutation) Token() (r []byte, exists bool) {
v := m.token
if v == nil {
return
}
return *v, true
}
// OldToken returns the old "token" field's value of the ActionToken entity.
// If the ActionToken object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ActionTokenMutation) OldToken(ctx context.Context) (v []byte, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldToken is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldToken requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldToken: %w", err)
}
return oldValue.Token, nil
}
// ResetToken resets all changes to the "token" field.
func (m *ActionTokenMutation) ResetToken() {
m.token = nil
}
// ClearUser clears the "user" edge to the User entity.
func (m *ActionTokenMutation) ClearUser() {
m.cleareduser = true
m.clearedFields[actiontoken.FieldUserID] = struct{}{}
}
// UserCleared reports if the "user" edge to the User entity was cleared.
func (m *ActionTokenMutation) UserCleared() bool {
return m.cleareduser
}
// UserIDs returns the "user" edge IDs in the mutation.
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
// UserID instead. It exists only for internal usage by the builders.
func (m *ActionTokenMutation) UserIDs() (ids []uuid.UUID) {
if id := m.user; id != nil {
ids = append(ids, *id)
}
return
}
// ResetUser resets all changes to the "user" edge.
func (m *ActionTokenMutation) ResetUser() {
m.user = nil
m.cleareduser = false
}
// Where appends a list predicates to the ActionTokenMutation builder.
func (m *ActionTokenMutation) Where(ps ...predicate.ActionToken) {
m.predicates = append(m.predicates, ps...)
}
// WhereP appends storage-level predicates to the ActionTokenMutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
func (m *ActionTokenMutation) WhereP(ps ...func(*sql.Selector)) {
p := make([]predicate.ActionToken, len(ps))
for i := range ps {
p[i] = ps[i]
}
m.Where(p...)
}
// Op returns the operation name.
func (m *ActionTokenMutation) Op() Op {
return m.op
}
// SetOp allows setting the mutation operation.
func (m *ActionTokenMutation) SetOp(op Op) {
m.op = op
}
// Type returns the node type of this mutation (ActionToken).
func (m *ActionTokenMutation) Type() string {
return m.typ
}
// Fields returns all fields that were changed during this mutation. Note that in
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *ActionTokenMutation) Fields() []string {
fields := make([]string, 0, 5)
if m.user != nil {
fields = append(fields, actiontoken.FieldUserID)
}
if m.created_at != nil {
fields = append(fields, actiontoken.FieldCreatedAt)
}
if m.updated_at != nil {
fields = append(fields, actiontoken.FieldUpdatedAt)
}
if m.action != nil {
fields = append(fields, actiontoken.FieldAction)
}
if m.token != nil {
fields = append(fields, actiontoken.FieldToken)
}
return fields
}
// Field returns the value of a field with the given name. The second boolean
// return value indicates that this field was not set, or was not defined in the
// schema.
func (m *ActionTokenMutation) Field(name string) (ent.Value, bool) {
switch name {
case actiontoken.FieldUserID:
return m.UserID()
case actiontoken.FieldCreatedAt:
return m.CreatedAt()
case actiontoken.FieldUpdatedAt:
return m.UpdatedAt()
case actiontoken.FieldAction:
return m.Action()
case actiontoken.FieldToken:
return m.Token()
}
return nil, false
}
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
func (m *ActionTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
case actiontoken.FieldUserID:
return m.OldUserID(ctx)
case actiontoken.FieldCreatedAt:
return m.OldCreatedAt(ctx)
case actiontoken.FieldUpdatedAt:
return m.OldUpdatedAt(ctx)
case actiontoken.FieldAction:
return m.OldAction(ctx)
case actiontoken.FieldToken:
return m.OldToken(ctx)
}
return nil, fmt.Errorf("unknown ActionToken field %s", name)
}
// SetField sets the value of a field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
func (m *ActionTokenMutation) SetField(name string, value ent.Value) error {
switch name {
case actiontoken.FieldUserID:
v, ok := value.(uuid.UUID)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetUserID(v)
return nil
case actiontoken.FieldCreatedAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetCreatedAt(v)
return nil
case actiontoken.FieldUpdatedAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetUpdatedAt(v)
return nil
case actiontoken.FieldAction:
v, ok := value.(actiontoken.Action)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetAction(v)
return nil
case actiontoken.FieldToken:
v, ok := value.([]byte)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetToken(v)
return nil
}
return fmt.Errorf("unknown ActionToken field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
func (m *ActionTokenMutation) AddedFields() []string {
return nil
}
// AddedField returns the numeric value that was incremented/decremented on a field
// with the given name. The second boolean return value indicates that this field
// was not set, or was not defined in the schema.
func (m *ActionTokenMutation) AddedField(name string) (ent.Value, bool) {
return nil, false
}
// AddField adds the value to the field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
func (m *ActionTokenMutation) AddField(name string, value ent.Value) error {
switch name {
}
return fmt.Errorf("unknown ActionToken numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *ActionTokenMutation) ClearedFields() []string {
return nil
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
func (m *ActionTokenMutation) FieldCleared(name string) bool {
_, ok := m.clearedFields[name]
return ok
}
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
func (m *ActionTokenMutation) ClearField(name string) error {
return fmt.Errorf("unknown ActionToken nullable field %s", name)
}
// ResetField resets all changes in the mutation for the field with the given name.
// It returns an error if the field is not defined in the schema.
func (m *ActionTokenMutation) ResetField(name string) error {
switch name {
case actiontoken.FieldUserID:
m.ResetUserID()
return nil
case actiontoken.FieldCreatedAt:
m.ResetCreatedAt()
return nil
case actiontoken.FieldUpdatedAt:
m.ResetUpdatedAt()
return nil
case actiontoken.FieldAction:
m.ResetAction()
return nil
case actiontoken.FieldToken:
m.ResetToken()
return nil
}
return fmt.Errorf("unknown ActionToken field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *ActionTokenMutation) AddedEdges() []string {
edges := make([]string, 0, 1)
if m.user != nil {
edges = append(edges, actiontoken.EdgeUser)
}
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
func (m *ActionTokenMutation) AddedIDs(name string) []ent.Value {
switch name {
case actiontoken.EdgeUser:
if id := m.user; id != nil {
return []ent.Value{*id}
}
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *ActionTokenMutation) RemovedEdges() []string {
edges := make([]string, 0, 1)
return edges
}
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
// the given name in this mutation.
func (m *ActionTokenMutation) RemovedIDs(name string) []ent.Value {
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *ActionTokenMutation) ClearedEdges() []string {
edges := make([]string, 0, 1)
if m.cleareduser {
edges = append(edges, actiontoken.EdgeUser)
}
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
func (m *ActionTokenMutation) EdgeCleared(name string) bool {
switch name {
case actiontoken.EdgeUser:
return m.cleareduser
}
return false
}
// ClearEdge clears the value of the edge with the given name. It returns an error
// if that edge is not defined in the schema.
func (m *ActionTokenMutation) ClearEdge(name string) error {
switch name {
case actiontoken.EdgeUser:
m.ClearUser()
return nil
}
return fmt.Errorf("unknown ActionToken unique edge %s", name)
}
// ResetEdge resets all changes to the edge with the given name in this mutation.
// It returns an error if the edge is not defined in the schema.
func (m *ActionTokenMutation) ResetEdge(name string) error {
switch name {
case actiontoken.EdgeUser:
m.ResetUser()
return nil
}
return fmt.Errorf("unknown ActionToken edge %s", name)
}
// AttachmentMutation represents an operation that mutates the Attachment nodes in the graph.
type AttachmentMutation struct {
config
@@ -2340,7 +2944,7 @@ type GroupMutation struct {
created_at *time.Time
updated_at *time.Time
name *string
currency *group.Currency
currency *string
clearedFields map[string]struct{}
users map[uuid.UUID]struct{}
removedusers map[uuid.UUID]struct{}
@@ -2581,12 +3185,12 @@ func (m *GroupMutation) ResetName() {
}
// SetCurrency sets the "currency" field.
func (m *GroupMutation) SetCurrency(gr group.Currency) {
m.currency = &gr
func (m *GroupMutation) SetCurrency(s string) {
m.currency = &s
}
// Currency returns the value of the "currency" field in the mutation.
func (m *GroupMutation) Currency() (r group.Currency, exists bool) {
func (m *GroupMutation) Currency() (r string, exists bool) {
v := m.currency
if v == nil {
return
@@ -2597,7 +3201,7 @@ func (m *GroupMutation) Currency() (r group.Currency, exists bool) {
// OldCurrency returns the old "currency" field's value of the Group entity.
// If the Group object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *GroupMutation) OldCurrency(ctx context.Context) (v group.Currency, err error) {
func (m *GroupMutation) OldCurrency(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldCurrency is only allowed on UpdateOne operations")
}
@@ -3105,7 +3709,7 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error {
m.SetName(v)
return nil
case group.FieldCurrency:
v, ok := value.(group.Currency)
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
@@ -10672,30 +11276,33 @@ func (m *NotifierMutation) ResetEdge(name string) error {
// UserMutation represents an operation that mutates the User nodes in the graph.
type UserMutation struct {
config
op Op
typ string
id *uuid.UUID
created_at *time.Time
updated_at *time.Time
name *string
email *string
password *string
is_superuser *bool
superuser *bool
role *user.Role
activated_on *time.Time
clearedFields map[string]struct{}
group *uuid.UUID
clearedgroup bool
auth_tokens map[uuid.UUID]struct{}
removedauth_tokens map[uuid.UUID]struct{}
clearedauth_tokens bool
notifiers map[uuid.UUID]struct{}
removednotifiers map[uuid.UUID]struct{}
clearednotifiers bool
done bool
oldValue func(context.Context) (*User, error)
predicates []predicate.User
op Op
typ string
id *uuid.UUID
created_at *time.Time
updated_at *time.Time
name *string
email *string
password *string
is_superuser *bool
superuser *bool
role *user.Role
activated_on *time.Time
clearedFields map[string]struct{}
group *uuid.UUID
clearedgroup bool
auth_tokens map[uuid.UUID]struct{}
removedauth_tokens map[uuid.UUID]struct{}
clearedauth_tokens bool
notifiers map[uuid.UUID]struct{}
removednotifiers map[uuid.UUID]struct{}
clearednotifiers bool
action_tokens map[uuid.UUID]struct{}
removedaction_tokens map[uuid.UUID]struct{}
clearedaction_tokens bool
done bool
oldValue func(context.Context) (*User, error)
predicates []predicate.User
}
var _ ent.Mutation = (*UserMutation)(nil)
@@ -11286,6 +11893,60 @@ func (m *UserMutation) ResetNotifiers() {
m.removednotifiers = nil
}
// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by ids.
func (m *UserMutation) AddActionTokenIDs(ids ...uuid.UUID) {
if m.action_tokens == nil {
m.action_tokens = make(map[uuid.UUID]struct{})
}
for i := range ids {
m.action_tokens[ids[i]] = struct{}{}
}
}
// ClearActionTokens clears the "action_tokens" edge to the ActionToken entity.
func (m *UserMutation) ClearActionTokens() {
m.clearedaction_tokens = true
}
// ActionTokensCleared reports if the "action_tokens" edge to the ActionToken entity was cleared.
func (m *UserMutation) ActionTokensCleared() bool {
return m.clearedaction_tokens
}
// RemoveActionTokenIDs removes the "action_tokens" edge to the ActionToken entity by IDs.
func (m *UserMutation) RemoveActionTokenIDs(ids ...uuid.UUID) {
if m.removedaction_tokens == nil {
m.removedaction_tokens = make(map[uuid.UUID]struct{})
}
for i := range ids {
delete(m.action_tokens, ids[i])
m.removedaction_tokens[ids[i]] = struct{}{}
}
}
// RemovedActionTokens returns the removed IDs of the "action_tokens" edge to the ActionToken entity.
func (m *UserMutation) RemovedActionTokensIDs() (ids []uuid.UUID) {
for id := range m.removedaction_tokens {
ids = append(ids, id)
}
return
}
// ActionTokensIDs returns the "action_tokens" edge IDs in the mutation.
func (m *UserMutation) ActionTokensIDs() (ids []uuid.UUID) {
for id := range m.action_tokens {
ids = append(ids, id)
}
return
}
// ResetActionTokens resets all changes to the "action_tokens" edge.
func (m *UserMutation) ResetActionTokens() {
m.action_tokens = nil
m.clearedaction_tokens = false
m.removedaction_tokens = nil
}
// Where appends a list predicates to the UserMutation builder.
func (m *UserMutation) Where(ps ...predicate.User) {
m.predicates = append(m.predicates, ps...)
@@ -11564,7 +12225,7 @@ func (m *UserMutation) ResetField(name string) error {
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *UserMutation) AddedEdges() []string {
edges := make([]string, 0, 3)
edges := make([]string, 0, 4)
if m.group != nil {
edges = append(edges, user.EdgeGroup)
}
@@ -11574,6 +12235,9 @@ func (m *UserMutation) AddedEdges() []string {
if m.notifiers != nil {
edges = append(edges, user.EdgeNotifiers)
}
if m.action_tokens != nil {
edges = append(edges, user.EdgeActionTokens)
}
return edges
}
@@ -11597,19 +12261,28 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
case user.EdgeActionTokens:
ids := make([]ent.Value, 0, len(m.action_tokens))
for id := range m.action_tokens {
ids = append(ids, id)
}
return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *UserMutation) RemovedEdges() []string {
edges := make([]string, 0, 3)
edges := make([]string, 0, 4)
if m.removedauth_tokens != nil {
edges = append(edges, user.EdgeAuthTokens)
}
if m.removednotifiers != nil {
edges = append(edges, user.EdgeNotifiers)
}
if m.removedaction_tokens != nil {
edges = append(edges, user.EdgeActionTokens)
}
return edges
}
@@ -11629,13 +12302,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
case user.EdgeActionTokens:
ids := make([]ent.Value, 0, len(m.removedaction_tokens))
for id := range m.removedaction_tokens {
ids = append(ids, id)
}
return ids
}
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *UserMutation) ClearedEdges() []string {
edges := make([]string, 0, 3)
edges := make([]string, 0, 4)
if m.clearedgroup {
edges = append(edges, user.EdgeGroup)
}
@@ -11645,6 +12324,9 @@ func (m *UserMutation) ClearedEdges() []string {
if m.clearednotifiers {
edges = append(edges, user.EdgeNotifiers)
}
if m.clearedaction_tokens {
edges = append(edges, user.EdgeActionTokens)
}
return edges
}
@@ -11658,6 +12340,8 @@ func (m *UserMutation) EdgeCleared(name string) bool {
return m.clearedauth_tokens
case user.EdgeNotifiers:
return m.clearednotifiers
case user.EdgeActionTokens:
return m.clearedaction_tokens
}
return false
}
@@ -11686,6 +12370,9 @@ func (m *UserMutation) ResetEdge(name string) error {
case user.EdgeNotifiers:
m.ResetNotifiers()
return nil
case user.EdgeActionTokens:
m.ResetActionTokens()
return nil
}
return fmt.Errorf("unknown User edge %s", name)
}

View File

@@ -6,6 +6,9 @@ import (
"entgo.io/ent/dialect/sql"
)
// ActionToken is the predicate function for actiontoken builders.
type ActionToken func(*sql.Selector)
// Attachment is the predicate function for attachment builders.
type Attachment func(*sql.Selector)

View File

@@ -6,6 +6,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
@@ -25,6 +26,25 @@ import (
// (default values, validators, hooks and policies) and stitches it
// to their package variables.
func init() {
actiontokenMixin := schema.ActionToken{}.Mixin()
actiontokenMixinFields1 := actiontokenMixin[1].Fields()
_ = actiontokenMixinFields1
actiontokenFields := schema.ActionToken{}.Fields()
_ = actiontokenFields
// actiontokenDescCreatedAt is the schema descriptor for created_at field.
actiontokenDescCreatedAt := actiontokenMixinFields1[1].Descriptor()
// actiontoken.DefaultCreatedAt holds the default value on creation for the created_at field.
actiontoken.DefaultCreatedAt = actiontokenDescCreatedAt.Default.(func() time.Time)
// actiontokenDescUpdatedAt is the schema descriptor for updated_at field.
actiontokenDescUpdatedAt := actiontokenMixinFields1[2].Descriptor()
// actiontoken.DefaultUpdatedAt holds the default value on creation for the updated_at field.
actiontoken.DefaultUpdatedAt = actiontokenDescUpdatedAt.Default.(func() time.Time)
// actiontoken.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
actiontoken.UpdateDefaultUpdatedAt = actiontokenDescUpdatedAt.UpdateDefault.(func() time.Time)
// actiontokenDescID is the schema descriptor for id field.
actiontokenDescID := actiontokenMixinFields1[0].Descriptor()
// actiontoken.DefaultID holds the default value on creation for the id field.
actiontoken.DefaultID = actiontokenDescID.Default.(func() uuid.UUID)
attachmentMixin := schema.Attachment{}.Mixin()
attachmentMixinFields0 := attachmentMixin[0].Fields()
_ = attachmentMixinFields0
@@ -161,6 +181,10 @@ func init() {
return nil
}
}()
// groupDescCurrency is the schema descriptor for currency field.
groupDescCurrency := groupFields[1].Descriptor()
// group.DefaultCurrency holds the default value on creation for the currency field.
group.DefaultCurrency = groupDescCurrency.Default.(string)
// groupDescID is the schema descriptor for id field.
groupDescID := groupMixinFields0[0].Descriptor()
// group.DefaultID holds the default value on creation for the id field.

View File

@@ -0,0 +1,42 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"github.com/hay-kot/homebox/backend/internal/data/ent/schema/mixins"
)
type ActionToken struct {
ent.Schema
}
func (ActionToken) Mixin() []ent.Mixin {
return []ent.Mixin{
UserMixin{
ref: "action_tokens",
field: "user_id",
},
mixins.BaseMixin{},
}
}
// Fields of the ActionToken.
func (ActionToken) Fields() []ent.Field {
return []ent.Field{
field.Enum("action").
Values("reset_password").
Default("reset_password"),
field.Bytes("token").
Unique(),
}
}
func (ActionToken) Indexes() []ent.Index {
return []ent.Index{
index.Fields("token"),
index.Fields("action"),
index.Fields("user_id"),
}
}

View File

@@ -27,41 +27,8 @@ func (Group) Fields() []ent.Field {
field.String("name").
MaxLen(255).
NotEmpty(),
field.Enum("currency").
Default("usd").
Values(
"aed",
"aud",
"bgn",
"brl",
"cad",
"chf",
"czk",
"dkk",
"eur",
"gbp",
"hkd",
"idr",
"inr",
"jpy",
"krw",
"mxn",
"nok",
"nzd",
"pln",
"rmb",
"ron",
"rub",
"sar",
"sek",
"sgd",
"thb",
"try",
"usd",
"xag",
"xau",
"zar",
),
field.String("currency").
Default("usd"),
}
}

View File

@@ -20,4 +20,4 @@ import "github.com/google/uuid"
}
{{ end }}
{{ end }}
{{ end }}

View File

@@ -52,13 +52,11 @@ func (User) Fields() []ent.Field {
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("auth_tokens", AuthTokens.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
Annotations(entsql.Annotation{OnDelete: entsql.Cascade}),
edge.To("notifiers", Notifier.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
Annotations(entsql.Annotation{OnDelete: entsql.Cascade}),
edge.To("action_tokens", ActionToken.Type).
Annotations(entsql.Annotation{OnDelete: entsql.Cascade}),
}
}
@@ -78,7 +76,6 @@ func (g UserMixin) Fields() []ent.Field {
}
return nil
}
func (g UserMixin) Edges() []ent.Edge {

View File

@@ -12,6 +12,8 @@ import (
// Tx is a transactional client that is created by calling Client.Tx().
type Tx struct {
config
// ActionToken is the client for interacting with the ActionToken builders.
ActionToken *ActionTokenClient
// Attachment is the client for interacting with the Attachment builders.
Attachment *AttachmentClient
// AuthRoles is the client for interacting with the AuthRoles builders.
@@ -169,6 +171,7 @@ func (tx *Tx) Client() *Client {
}
func (tx *Tx) init() {
tx.ActionToken = NewActionTokenClient(tx.config)
tx.Attachment = NewAttachmentClient(tx.config)
tx.AuthRoles = NewAuthRolesClient(tx.config)
tx.AuthTokens = NewAuthTokensClient(tx.config)
@@ -191,7 +194,7 @@ func (tx *Tx) init() {
// of them in order to commit or rollback the transaction.
//
// If a closed transaction is embedded in one of the generated entities, and the entity
// applies a query, for example: Attachment.QueryXXX(), the query will be executed
// applies a query, for example: ActionToken.QueryXXX(), the query will be executed
// through the driver which created this transaction.
//
// Note that txDriver is not goroutine safe.

View File

@@ -52,9 +52,11 @@ type UserEdges struct {
AuthTokens []*AuthTokens `json:"auth_tokens,omitempty"`
// Notifiers holds the value of the notifiers edge.
Notifiers []*Notifier `json:"notifiers,omitempty"`
// ActionTokens holds the value of the action_tokens edge.
ActionTokens []*ActionToken `json:"action_tokens,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [3]bool
loadedTypes [4]bool
}
// GroupOrErr returns the Group value or an error if the edge
@@ -88,6 +90,15 @@ func (e UserEdges) NotifiersOrErr() ([]*Notifier, error) {
return nil, &NotLoadedError{edge: "notifiers"}
}
// ActionTokensOrErr returns the ActionTokens value or an error if the edge
// was not loaded in eager-loading.
func (e UserEdges) ActionTokensOrErr() ([]*ActionToken, error) {
if e.loadedTypes[3] {
return e.ActionTokens, nil
}
return nil, &NotLoadedError{edge: "action_tokens"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*User) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
@@ -213,6 +224,11 @@ func (u *User) QueryNotifiers() *NotifierQuery {
return NewUserClient(u.config).QueryNotifiers(u)
}
// QueryActionTokens queries the "action_tokens" edge of the User entity.
func (u *User) QueryActionTokens() *ActionTokenQuery {
return NewUserClient(u.config).QueryActionTokens(u)
}
// Update returns a builder for updating this User.
// Note that you need to call User.Unwrap() before calling this method if this User
// was returned from a transaction, and the transaction was committed or rolled back.

View File

@@ -40,6 +40,8 @@ const (
EdgeAuthTokens = "auth_tokens"
// EdgeNotifiers holds the string denoting the notifiers edge name in mutations.
EdgeNotifiers = "notifiers"
// EdgeActionTokens holds the string denoting the action_tokens edge name in mutations.
EdgeActionTokens = "action_tokens"
// Table holds the table name of the user in the database.
Table = "users"
// GroupTable is the table that holds the group relation/edge.
@@ -63,6 +65,13 @@ const (
NotifiersInverseTable = "notifiers"
// NotifiersColumn is the table column denoting the notifiers relation/edge.
NotifiersColumn = "user_id"
// ActionTokensTable is the table that holds the action_tokens relation/edge.
ActionTokensTable = "action_tokens"
// ActionTokensInverseTable is the table name for the ActionToken entity.
// It exists in this package in order to avoid circular dependency with the "actiontoken" package.
ActionTokensInverseTable = "action_tokens"
// ActionTokensColumn is the table column denoting the action_tokens relation/edge.
ActionTokensColumn = "user_id"
)
// Columns holds all SQL columns for user fields.
@@ -234,6 +243,20 @@ func ByNotifiers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
sqlgraph.OrderByNeighborTerms(s, newNotifiersStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByActionTokensCount orders the results by action_tokens count.
func ByActionTokensCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newActionTokensStep(), opts...)
}
}
// ByActionTokens orders the results by action_tokens terms.
func ByActionTokens(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newActionTokensStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newGroupStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
@@ -255,3 +278,10 @@ func newNotifiersStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn),
)
}
func newActionTokensStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ActionTokensInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ActionTokensTable, ActionTokensColumn),
)
}

View File

@@ -530,6 +530,29 @@ func HasNotifiersWith(preds ...predicate.Notifier) predicate.User {
})
}
// HasActionTokens applies the HasEdge predicate on the "action_tokens" edge.
func HasActionTokens() predicate.User {
return predicate.User(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ActionTokensTable, ActionTokensColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasActionTokensWith applies the HasEdge predicate on the "action_tokens" edge with a given conditions (other predicates).
func HasActionTokensWith(preds ...predicate.ActionToken) predicate.User {
return predicate.User(func(s *sql.Selector) {
step := newActionTokensStep()
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.User) predicate.User {
return predicate.User(sql.AndPredicates(predicates...))

View File

@@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
@@ -181,6 +182,21 @@ func (uc *UserCreate) AddNotifiers(n ...*Notifier) *UserCreate {
return uc.AddNotifierIDs(ids...)
}
// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by IDs.
func (uc *UserCreate) AddActionTokenIDs(ids ...uuid.UUID) *UserCreate {
uc.mutation.AddActionTokenIDs(ids...)
return uc
}
// AddActionTokens adds the "action_tokens" edges to the ActionToken entity.
func (uc *UserCreate) AddActionTokens(a ...*ActionToken) *UserCreate {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return uc.AddActionTokenIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (uc *UserCreate) Mutation() *UserMutation {
return uc.mutation
@@ -411,6 +427,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := uc.mutation.ActionTokensIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.ActionTokensTable,
Columns: []string{user.ActionTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}

View File

@@ -12,6 +12,7 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
@@ -22,14 +23,15 @@ import (
// UserQuery is the builder for querying User entities.
type UserQuery struct {
config
ctx *QueryContext
order []user.OrderOption
inters []Interceptor
predicates []predicate.User
withGroup *GroupQuery
withAuthTokens *AuthTokensQuery
withNotifiers *NotifierQuery
withFKs bool
ctx *QueryContext
order []user.OrderOption
inters []Interceptor
predicates []predicate.User
withGroup *GroupQuery
withAuthTokens *AuthTokensQuery
withNotifiers *NotifierQuery
withActionTokens *ActionTokenQuery
withFKs bool
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -132,6 +134,28 @@ func (uq *UserQuery) QueryNotifiers() *NotifierQuery {
return query
}
// QueryActionTokens chains the current query on the "action_tokens" edge.
func (uq *UserQuery) QueryActionTokens() *ActionTokenQuery {
query := (&ActionTokenClient{config: uq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := uq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := uq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, selector),
sqlgraph.To(actiontoken.Table, actiontoken.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.ActionTokensTable, user.ActionTokensColumn),
)
fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first User entity from the query.
// Returns a *NotFoundError when no User was found.
func (uq *UserQuery) First(ctx context.Context) (*User, error) {
@@ -319,14 +343,15 @@ func (uq *UserQuery) Clone() *UserQuery {
return nil
}
return &UserQuery{
config: uq.config,
ctx: uq.ctx.Clone(),
order: append([]user.OrderOption{}, uq.order...),
inters: append([]Interceptor{}, uq.inters...),
predicates: append([]predicate.User{}, uq.predicates...),
withGroup: uq.withGroup.Clone(),
withAuthTokens: uq.withAuthTokens.Clone(),
withNotifiers: uq.withNotifiers.Clone(),
config: uq.config,
ctx: uq.ctx.Clone(),
order: append([]user.OrderOption{}, uq.order...),
inters: append([]Interceptor{}, uq.inters...),
predicates: append([]predicate.User{}, uq.predicates...),
withGroup: uq.withGroup.Clone(),
withAuthTokens: uq.withAuthTokens.Clone(),
withNotifiers: uq.withNotifiers.Clone(),
withActionTokens: uq.withActionTokens.Clone(),
// clone intermediate query.
sql: uq.sql.Clone(),
path: uq.path,
@@ -366,6 +391,17 @@ func (uq *UserQuery) WithNotifiers(opts ...func(*NotifierQuery)) *UserQuery {
return uq
}
// WithActionTokens tells the query-builder to eager-load the nodes that are connected to
// the "action_tokens" edge. The optional arguments are used to configure the query builder of the edge.
func (uq *UserQuery) WithActionTokens(opts ...func(*ActionTokenQuery)) *UserQuery {
query := (&ActionTokenClient{config: uq.config}).Query()
for _, opt := range opts {
opt(query)
}
uq.withActionTokens = query
return uq
}
// 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.
//
@@ -445,10 +481,11 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
nodes = []*User{}
withFKs = uq.withFKs
_spec = uq.querySpec()
loadedTypes = [3]bool{
loadedTypes = [4]bool{
uq.withGroup != nil,
uq.withAuthTokens != nil,
uq.withNotifiers != nil,
uq.withActionTokens != nil,
}
)
if uq.withGroup != nil {
@@ -495,6 +532,13 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
return nil, err
}
}
if query := uq.withActionTokens; query != nil {
if err := uq.loadActionTokens(ctx, query, nodes,
func(n *User) { n.Edges.ActionTokens = []*ActionToken{} },
func(n *User, e *ActionToken) { n.Edges.ActionTokens = append(n.Edges.ActionTokens, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
@@ -591,6 +635,36 @@ func (uq *UserQuery) loadNotifiers(ctx context.Context, query *NotifierQuery, no
}
return nil
}
func (uq *UserQuery) loadActionTokens(ctx context.Context, query *ActionTokenQuery, nodes []*User, init func(*User), assign func(*User, *ActionToken)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*User)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(actiontoken.FieldUserID)
}
query.Where(predicate.ActionToken(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(user.ActionTokensColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.UserID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) {
_spec := uq.querySpec()

View File

@@ -12,6 +12,7 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
@@ -183,6 +184,21 @@ func (uu *UserUpdate) AddNotifiers(n ...*Notifier) *UserUpdate {
return uu.AddNotifierIDs(ids...)
}
// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by IDs.
func (uu *UserUpdate) AddActionTokenIDs(ids ...uuid.UUID) *UserUpdate {
uu.mutation.AddActionTokenIDs(ids...)
return uu
}
// AddActionTokens adds the "action_tokens" edges to the ActionToken entity.
func (uu *UserUpdate) AddActionTokens(a ...*ActionToken) *UserUpdate {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return uu.AddActionTokenIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (uu *UserUpdate) Mutation() *UserMutation {
return uu.mutation
@@ -236,6 +252,27 @@ func (uu *UserUpdate) RemoveNotifiers(n ...*Notifier) *UserUpdate {
return uu.RemoveNotifierIDs(ids...)
}
// ClearActionTokens clears all "action_tokens" edges to the ActionToken entity.
func (uu *UserUpdate) ClearActionTokens() *UserUpdate {
uu.mutation.ClearActionTokens()
return uu
}
// RemoveActionTokenIDs removes the "action_tokens" edge to ActionToken entities by IDs.
func (uu *UserUpdate) RemoveActionTokenIDs(ids ...uuid.UUID) *UserUpdate {
uu.mutation.RemoveActionTokenIDs(ids...)
return uu
}
// RemoveActionTokens removes "action_tokens" edges to ActionToken entities.
func (uu *UserUpdate) RemoveActionTokens(a ...*ActionToken) *UserUpdate {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return uu.RemoveActionTokenIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (uu *UserUpdate) Save(ctx context.Context) (int, error) {
uu.defaults()
@@ -458,6 +495,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if uu.mutation.ActionTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.ActionTokensTable,
Columns: []string{user.ActionTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uu.mutation.RemovedActionTokensIDs(); len(nodes) > 0 && !uu.mutation.ActionTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.ActionTokensTable,
Columns: []string{user.ActionTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(actiontoken.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 := uu.mutation.ActionTokensIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.ActionTokensTable,
Columns: []string{user.ActionTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{user.Label}
@@ -629,6 +711,21 @@ func (uuo *UserUpdateOne) AddNotifiers(n ...*Notifier) *UserUpdateOne {
return uuo.AddNotifierIDs(ids...)
}
// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by IDs.
func (uuo *UserUpdateOne) AddActionTokenIDs(ids ...uuid.UUID) *UserUpdateOne {
uuo.mutation.AddActionTokenIDs(ids...)
return uuo
}
// AddActionTokens adds the "action_tokens" edges to the ActionToken entity.
func (uuo *UserUpdateOne) AddActionTokens(a ...*ActionToken) *UserUpdateOne {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return uuo.AddActionTokenIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (uuo *UserUpdateOne) Mutation() *UserMutation {
return uuo.mutation
@@ -682,6 +779,27 @@ func (uuo *UserUpdateOne) RemoveNotifiers(n ...*Notifier) *UserUpdateOne {
return uuo.RemoveNotifierIDs(ids...)
}
// ClearActionTokens clears all "action_tokens" edges to the ActionToken entity.
func (uuo *UserUpdateOne) ClearActionTokens() *UserUpdateOne {
uuo.mutation.ClearActionTokens()
return uuo
}
// RemoveActionTokenIDs removes the "action_tokens" edge to ActionToken entities by IDs.
func (uuo *UserUpdateOne) RemoveActionTokenIDs(ids ...uuid.UUID) *UserUpdateOne {
uuo.mutation.RemoveActionTokenIDs(ids...)
return uuo
}
// RemoveActionTokens removes "action_tokens" edges to ActionToken entities.
func (uuo *UserUpdateOne) RemoveActionTokens(a ...*ActionToken) *UserUpdateOne {
ids := make([]uuid.UUID, len(a))
for i := range a {
ids[i] = a[i].ID
}
return uuo.RemoveActionTokenIDs(ids...)
}
// Where appends a list predicates to the UserUpdate builder.
func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne {
uuo.mutation.Where(ps...)
@@ -934,6 +1052,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if uuo.mutation.ActionTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.ActionTokensTable,
Columns: []string{user.ActionTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uuo.mutation.RemovedActionTokensIDs(); len(nodes) > 0 && !uuo.mutation.ActionTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.ActionTokensTable,
Columns: []string{user.ActionTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(actiontoken.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 := uuo.mutation.ActionTokensIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.ActionTokensTable,
Columns: []string{user.ActionTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &User{config: uuo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues

View File

@@ -1,3 +1,4 @@
// Package migrations provides a way to embed the migrations into the binary.
package migrations
import (

View File

@@ -0,0 +1,10 @@
-- Create "action_tokens" table
CREATE TABLE `action_tokens` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `action` text NOT NULL DEFAULT ('reset_password'), `token` blob NOT NULL, `user_id` uuid NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `action_tokens_users_action_tokens` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE);
-- Create index "action_tokens_token_key" to table: "action_tokens"
CREATE UNIQUE INDEX `action_tokens_token_key` ON `action_tokens` (`token`);
-- Create index "actiontoken_token" to table: "action_tokens"
CREATE INDEX `actiontoken_token` ON `action_tokens` (`token`);
-- Create index "actiontoken_action" to table: "action_tokens"
CREATE INDEX `actiontoken_action` ON `action_tokens` (`action`);
-- Create index "actiontoken_user_id" to table: "action_tokens"
CREATE INDEX `actiontoken_user_id` ON `action_tokens` (`user_id`);

View File

@@ -1,4 +1,4 @@
h1:sjJCTAqc9FG8BKBIzh5ZynYD/Ilz6vnLqM4XX83WQ4M=
h1:6CJ6qlt5IqAoKF6R9yH8Ei2eqkAbkCqjM1dDUvA24f8=
20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q=
20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw=
20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU=
@@ -13,3 +13,4 @@ h1:sjJCTAqc9FG8BKBIzh5ZynYD/Ilz6vnLqM4XX83WQ4M=
20230305065819_add_notifier_types.sql h1:r5xrgCKYQ2o9byBqYeAX1zdp94BLdaxf4vq9OmGHNl0=
20230305071524_add_group_id_to_notifiers.sql h1:xDShqbyClcFhvJbwclOHdczgXbdffkxXNWjV61hL/t4=
20231006213457_add_primary_attachment_flag.sql h1:J4tMSJQFa7vaj0jpnh8YKTssdyIjRyq6RXDXZIzDDu4=
20240302172225_user_action_tokens.sql h1:nAdtVdh9O7p/AyKnURvZcS0H/Zoj1sM9HpwCOO09zDE=

View File

@@ -16,9 +16,9 @@ func (aid AssetID) Int() int {
return int(aid)
}
func ParseAssetIDBytes(d []byte) (AID AssetID, ok bool) {
d = bytes.Replace(d, []byte(`"`), []byte(``), -1)
d = bytes.Replace(d, []byte(`-`), []byte(``), -1)
func ParseAssetIDBytes(d []byte) (assetID AssetID, ok bool) {
d = bytes.ReplaceAll(d, []byte(`"`), []byte(``))
d = bytes.ReplaceAll(d, []byte(`-`), []byte(``))
aidInt, err := strconv.Atoi(string(d))
if err != nil {
@@ -28,7 +28,7 @@ func ParseAssetIDBytes(d []byte) (AID AssetID, ok bool) {
return AssetID(aidInt), true
}
func ParseAssetID(s string) (AID AssetID, ok bool) {
func ParseAssetID(s string) (assetID AssetID, ok bool) {
return ParseAssetIDBytes([]byte(s))
}
@@ -52,8 +52,8 @@ func (aid *AssetID) UnmarshalJSON(d []byte) error {
return nil
}
d = bytes.Replace(d, []byte(`"`), []byte(``), -1)
d = bytes.Replace(d, []byte(`-`), []byte(``), -1)
d = bytes.ReplaceAll(d, []byte(`"`), []byte(``))
d = bytes.ReplaceAll(d, []byte(`-`), []byte(``))
aidInt, err := strconv.Atoi(string(d))
if err != nil {

View File

@@ -45,7 +45,9 @@ func TestMain(m *testing.M) {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
go tbus.Run()
go func() {
_ = tbus.Run(context.Background())
}()
err = client.Schema.Create(context.Background())
if err != nil {
@@ -54,9 +56,10 @@ func TestMain(m *testing.M) {
tClient = client
tRepos = New(tClient, tbus, os.TempDir())
defer client.Close()
bootstrap()
os.Exit(m.Run())
exit := m.Run()
_ = client.Close()
os.Exit(exit)
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func useDocs(t *testing.T, num int) []DocumentOut {
@@ -25,7 +26,7 @@ func useDocs(t *testing.T, num int) []DocumentOut {
Content: bytes.NewReader([]byte(fk.Str(10))),
})
assert.NoError(t, err)
require.NoError(t, err)
assert.NotNil(t, doc)
results = append(results, doc)
ids = append(ids, doc.ID)
@@ -80,31 +81,31 @@ func TestDocumentRepository_CreateUpdateDelete(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Create Document
got, err := r.Create(tt.args.ctx, tt.args.gid, tt.args.doc)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, tt.title, got.Title)
assert.Equal(t, fmt.Sprintf("%s/%s/documents", temp, tt.args.gid), filepath.Dir(got.Path))
ensureRead := func() {
// Read Document
bts, err := os.ReadFile(got.Path)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, tt.content, string(bts))
}
ensureRead()
// Update Document
got, err = r.Rename(tt.args.ctx, got.ID, "__"+tt.title+"__")
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, "__"+tt.title+"__", got.Title)
ensureRead()
// Delete Document
err = r.Delete(tt.args.ctx, got.ID)
assert.NoError(t, err)
require.NoError(t, err)
_, err = os.Stat(got.Path)
assert.Error(t, err)
require.Error(t, err)
})
}
}

View File

@@ -28,7 +28,7 @@ func NewGroupRepository(db *ent.Client) *GroupRepository {
Name: g.Name,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,
Currency: strings.ToUpper(g.Currency.String()),
Currency: strings.ToUpper(g.Currency),
}
}
@@ -109,12 +109,12 @@ func (r *GroupRepository) GetAllGroups(ctx context.Context) ([]Group, error) {
return r.groupMapper.MapEachErr(r.db.Group.Query().All(ctx))
}
func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) {
func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, groupID uuid.UUID) ([]TotalsByOrganizer, error) {
var v []TotalsByOrganizer
err := r.db.Location.Query().
Where(
location.HasGroupWith(group.ID(GID)),
location.HasGroupWith(group.ID(groupID)),
).
GroupBy(location.FieldID, location.FieldName).
Aggregate(func(sq *sql.Selector) string {
@@ -131,12 +131,12 @@ func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, GID
return v, err
}
func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) {
func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, groupID uuid.UUID) ([]TotalsByOrganizer, error) {
var v []TotalsByOrganizer
err := r.db.Label.Query().
Where(
label.HasGroupWith(group.ID(GID)),
label.HasGroupWith(group.ID(groupID)),
).
GroupBy(label.FieldID, label.FieldName).
Aggregate(func(sq *sql.Selector) string {
@@ -157,7 +157,7 @@ func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, GID uu
return v, err
}
func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID, start, end time.Time) (*ValueOverTime, error) {
func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, groupID uuid.UUID, start, end time.Time) (*ValueOverTime, error) {
// Get the Totals for the Start and End of the Given Time Period
q := `
SELECT
@@ -180,7 +180,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID,
var maybeStart *float64
var maybeEnd *float64
row := r.db.Sql().QueryRowContext(ctx, q, GID, sqliteDateFormat(start), GID, sqliteDateFormat(end))
row := r.db.Sql().QueryRowContext(ctx, q, groupID, sqliteDateFormat(start), groupID, sqliteDateFormat(end))
err := row.Scan(&maybeStart, &maybeEnd)
if err != nil {
return nil, err
@@ -198,7 +198,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID,
// Get Created Date and Price of all items between start and end
err = r.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
item.CreatedAtGTE(start),
item.CreatedAtLTE(end),
item.Archived(false),
@@ -209,7 +209,6 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID,
item.FieldPurchasePrice,
).
Scan(ctx, &v)
if err != nil {
return nil, err
}
@@ -226,7 +225,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID,
return &stats, nil
}
func (r *GroupRepository) StatsGroup(ctx context.Context, GID uuid.UUID) (GroupStatistics, error) {
func (r *GroupRepository) StatsGroup(ctx context.Context, groupID uuid.UUID) (GroupStatistics, error) {
q := `
SELECT
(SELECT COUNT(*) FROM users WHERE group_users = ?) AS total_users,
@@ -242,7 +241,7 @@ func (r *GroupRepository) StatsGroup(ctx context.Context, GID uuid.UUID) (GroupS
) AS total_with_warranty
`
var stats GroupStatistics
row := r.db.Sql().QueryRowContext(ctx, q, GID, GID, GID, GID, GID, GID)
row := r.db.Sql().QueryRowContext(ctx, q, groupID, groupID, groupID, groupID, groupID, groupID)
var maybeTotalItemPrice *float64
var maybeTotalWithWarranty *int
@@ -264,19 +263,17 @@ func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group,
Save(ctx))
}
func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data GroupUpdate) (Group, error) {
currency := group.Currency(strings.ToLower(data.Currency))
entity, err := r.db.Group.UpdateOneID(ID).
func (r *GroupRepository) GroupUpdate(ctx context.Context, groupID uuid.UUID, data GroupUpdate) (Group, error) {
entity, err := r.db.Group.UpdateOneID(groupID).
SetName(data.Name).
SetCurrency(currency).
SetCurrency(strings.ToLower(data.Currency)).
Save(ctx)
return r.groupMapper.MapErr(entity, err)
}
func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
return r.groupMapper.MapErr(r.db.Group.Get(ctx, id))
func (r *GroupRepository) GroupByID(ctx context.Context, groupID uuid.UUID) (Group, error) {
return r.groupMapper.MapErr(r.db.Group.Get(ctx, groupID))
}
func (r *GroupRepository) InvitationGet(ctx context.Context, token []byte) (GroupInvitation, error) {

View File

@@ -5,29 +5,30 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Group_Create(t *testing.T) {
g, err := tRepos.Groups.GroupCreate(context.Background(), "test")
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, "test", g.Name)
// Get by ID
foundGroup, err := tRepos.Groups.GroupByID(context.Background(), g.ID)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, g.ID, foundGroup.ID)
}
func Test_Group_Update(t *testing.T) {
g, err := tRepos.Groups.GroupCreate(context.Background(), "test")
assert.NoError(t, err)
require.NoError(t, err)
g, err = tRepos.Groups.GroupUpdate(context.Background(), g.ID, GroupUpdate{
Name: "test2",
Currency: "eur",
})
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, "test2", g.Name)
assert.Equal(t, "EUR", g.Currency)
}
@@ -38,7 +39,7 @@ func Test_Group_GroupStatistics(t *testing.T) {
stats, err := tRepos.Groups.StatsGroup(context.Background(), tGroup.ID)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, 20, stats.TotalItems)
assert.Equal(t, 20, stats.TotalLabels)
assert.Equal(t, 1, stats.TotalUsers)

View File

@@ -51,31 +51,31 @@ func ToItemAttachment(attachment *ent.Attachment) ItemAttachment {
}
}
func (r *AttachmentRepo) Create(ctx context.Context, itemId, docId uuid.UUID, typ attachment.Type) (*ent.Attachment, error) {
func (r *AttachmentRepo) Create(ctx context.Context, itemID, docID uuid.UUID, typ attachment.Type) (*ent.Attachment, error) {
bldr := r.db.Attachment.Create().
SetType(typ).
SetDocumentID(docId).
SetItemID(itemId)
SetDocumentID(docID).
SetItemID(itemID)
// Autoset primary to true if this is the first attachment
// that is of type photo
if typ == attachment.TypePhoto {
cnt, err := r.db.Attachment.Query().
Where(
attachment.HasItemWith(item.ID(itemId)),
attachment.TypeEQ(typ),
).
Count(ctx)
if err != nil {
return nil, err
}
// Autoset primary to true if this is the first attachment
// that is of type photo
if typ == attachment.TypePhoto {
cnt, err := r.db.Attachment.Query().
Where(
attachment.HasItemWith(item.ID(itemID)),
attachment.TypeEQ(typ),
).
Count(ctx)
if err != nil {
return nil, err
}
if cnt == 0 {
bldr = bldr.SetPrimary(true)
}
}
if cnt == 0 {
bldr = bldr.SetPrimary(true)
}
}
return bldr.Save(ctx)
return bldr.Save(ctx)
}
func (r *AttachmentRepo) Get(ctx context.Context, id uuid.UUID) (*ent.Attachment, error) {
@@ -87,11 +87,11 @@ func (r *AttachmentRepo) Get(ctx context.Context, id uuid.UUID) (*ent.Attachment
Only(ctx)
}
func (r *AttachmentRepo) Update(ctx context.Context, itemId uuid.UUID, data *ItemAttachmentUpdate) (*ent.Attachment, error) {
func (r *AttachmentRepo) Update(ctx context.Context, itemID uuid.UUID, data *ItemAttachmentUpdate) (*ent.Attachment, error) {
// TODO: execute within Tx
typ := attachment.Type(data.Type)
bldr := r.db.Attachment.UpdateOneID(itemId).
bldr := r.db.Attachment.UpdateOneID(itemID).
SetType(typ)
// Primary only applies to photos
@@ -109,7 +109,7 @@ func (r *AttachmentRepo) Update(ctx context.Context, itemId uuid.UUID, data *Ite
// Ensure all other attachments are not primary
err = r.db.Attachment.Update().
Where(
attachment.HasItemWith(item.ID(itemId)),
attachment.HasItemWith(item.ID(itemID)),
attachment.IDNEQ(itm.ID),
).
SetPrimary(false).

View File

@@ -8,6 +8,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAttachmentRepo_Create(t *testing.T) {
@@ -23,8 +24,8 @@ func TestAttachmentRepo_Create(t *testing.T) {
type args struct {
ctx context.Context
itemId uuid.UUID
docId uuid.UUID
itemID uuid.UUID
docID uuid.UUID
typ attachment.Type
}
tests := []struct {
@@ -37,8 +38,8 @@ func TestAttachmentRepo_Create(t *testing.T) {
name: "create attachment",
args: args{
ctx: context.Background(),
itemId: item.ID,
docId: doc.ID,
itemID: item.ID,
docID: doc.ID,
typ: attachment.TypePhoto,
},
want: &ent.Attachment{
@@ -49,8 +50,8 @@ func TestAttachmentRepo_Create(t *testing.T) {
name: "create attachment with invalid item id",
args: args{
ctx: context.Background(),
itemId: uuid.New(),
docId: doc.ID,
itemID: uuid.New(),
docID: doc.ID,
typ: "blarg",
},
wantErr: true,
@@ -58,7 +59,7 @@ func TestAttachmentRepo_Create(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tRepos.Attachments.Create(tt.args.ctx, tt.args.itemId, tt.args.docId, tt.args.typ)
got, err := tRepos.Attachments.Create(tt.args.ctx, tt.args.itemID, tt.args.docID, tt.args.typ)
if (err != nil) != tt.wantErr {
t.Errorf("AttachmentRepo.Create() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -71,9 +72,9 @@ func TestAttachmentRepo_Create(t *testing.T) {
assert.Equal(t, tt.want.Type, got.Type)
withItems, err := tRepos.Attachments.Get(tt.args.ctx, got.ID)
assert.NoError(t, err)
assert.Equal(t, tt.args.itemId, withItems.Edges.Item.ID)
assert.Equal(t, tt.args.docId, withItems.Edges.Document.ID)
require.NoError(t, err)
assert.Equal(t, tt.args.itemID, withItems.Edges.Item.ID)
assert.Equal(t, tt.args.docID, withItems.Edges.Document.ID)
ids = append(ids, got.ID)
})
@@ -96,7 +97,7 @@ func useAttachments(t *testing.T, n int) []*ent.Attachment {
attachments := make([]*ent.Attachment, n)
for i := 0; i < n; i++ {
attachment, err := tRepos.Attachments.Create(context.Background(), item.ID, doc.ID, attachment.TypePhoto)
assert.NoError(t, err)
require.NoError(t, err)
attachments[i] = attachment
ids = append(ids, attachment.ID)
@@ -114,10 +115,10 @@ func TestAttachmentRepo_Update(t *testing.T) {
Type: string(typ),
})
assert.NoError(t, err)
require.NoError(t, err)
updated, err := tRepos.Attachments.Get(context.Background(), entity.ID)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, typ, updated.Type)
})
}
@@ -127,8 +128,8 @@ func TestAttachmentRepo_Delete(t *testing.T) {
entity := useAttachments(t, 1)[0]
err := tRepos.Attachments.Delete(context.Background(), entity.ID)
assert.NoError(t, err)
require.NoError(t, err)
_, err = tRepos.Attachments.Get(context.Background(), entity.ID)
assert.Error(t, err)
require.Error(t, err)
}

View File

@@ -55,8 +55,8 @@ type (
ItemCreate struct {
ImportRef string `json:"-"`
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"`
Name string `json:"name" validate:"required,min=1,max=255"`
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"`
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
AssetID AssetID `json:"-"`
@@ -66,9 +66,9 @@ type (
}
ItemUpdate struct {
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"`
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"`
ID uuid.UUID `json:"id"`
AssetID AssetID `json:"assetId"`
AssetID AssetID `json:"assetId" swaggertype:"string"`
Name string `json:"name"`
Description string `json:"description"`
Quantity int `json:"quantity"`
@@ -108,7 +108,7 @@ type (
ItemPatch struct {
ID uuid.UUID `json:"id"`
Quantity *int `json:"quantity,omitempty" extensions:"x-nullable,x-omitempty"`
ImportRef *string `json:"-,omitempty" extensions:"x-nullable,x-omitempty"`
ImportRef *string `json:"-,omitempty" extensions:"x-nullable,x-omitempty"`
}
ItemSummary struct {
@@ -276,9 +276,9 @@ func mapItemOut(item *ent.Item) ItemOut {
}
}
func (r *ItemsRepository) publishMutationEvent(GID uuid.UUID) {
if r.bus != nil {
r.bus.Publish(eventbus.EventItemMutation, eventbus.GroupMutationEvent{GID: GID})
func (e *ItemsRepository) publishMutationEvent(groupID uuid.UUID) {
if e.bus != nil {
e.bus.Publish(eventbus.EventItemMutation, eventbus.GroupMutationEvent{GID: groupID})
}
}
@@ -304,13 +304,13 @@ func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (ItemOut, er
return e.getOne(ctx, item.ID(id))
}
func (e *ItemsRepository) CheckRef(ctx context.Context, GID uuid.UUID, ref string) (bool, error) {
q := e.db.Item.Query().Where(item.HasGroupWith(group.ID(GID)))
func (e *ItemsRepository) CheckRef(ctx context.Context, groupID uuid.UUID, ref string) (bool, error) {
q := e.db.Item.Query().Where(item.HasGroupWith(group.ID(groupID)))
return q.Where(item.ImportRef(ref)).Exist(ctx)
}
func (e *ItemsRepository) GetByRef(ctx context.Context, GID uuid.UUID, ref string) (ItemOut, error) {
return e.getOne(ctx, item.ImportRef(ref), item.HasGroupWith(group.ID(GID)))
func (e *ItemsRepository) GetByRef(ctx context.Context, groupID uuid.UUID, ref string) (ItemOut, error) {
return e.getOne(ctx, item.ImportRef(ref), item.HasGroupWith(group.ID(groupID)))
}
// GetOneByGroup returns a single item by ID. If the item does not exist, an error is returned.
@@ -341,8 +341,10 @@ func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q Ite
item.Or(
item.NameContainsFold(q.Search),
item.DescriptionContainsFold(q.Search),
item.NotesContainsFold(q.Search),
item.SerialNumberContainsFold(q.Search),
item.ModelNumberContainsFold(q.Search),
item.ManufacturerContainsFold(q.Search),
item.NotesContainsFold(q.Search),
),
)
}
@@ -488,9 +490,9 @@ func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemOut,
All(ctx))
}
func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID) ([]ItemSummary, error) {
func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, groupID uuid.UUID) ([]ItemSummary, error) {
q := e.db.Item.Query().Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
item.AssetID(0),
).Order(
ent.Asc(item.FieldCreatedAt),
@@ -499,9 +501,9 @@ func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID)
return mapItemsSummaryErr(q.All(ctx))
}
func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID) (AssetID, error) {
func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, groupID uuid.UUID) (AssetID, error) {
q := e.db.Item.Query().Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
).Order(
ent.Desc(item.FieldAssetID),
).Limit(1)
@@ -517,10 +519,10 @@ func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID)
return AssetID(result.AssetID), nil
}
func (e *ItemsRepository) SetAssetID(ctx context.Context, GID uuid.UUID, ID uuid.UUID, assetID AssetID) error {
func (e *ItemsRepository) SetAssetID(ctx context.Context, groupID uuid.UUID, itemID uuid.UUID, assetID AssetID) error {
q := e.db.Item.Update().Where(
item.HasGroupWith(group.ID(GID)),
item.ID(ID),
item.HasGroupWith(group.ID(groupID)),
item.ID(itemID),
)
_, err := q.SetAssetID(int(assetID)).Save(ctx)
@@ -574,8 +576,8 @@ func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID)
return err
}
func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data ItemUpdate) (ItemOut, error) {
q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(GID))).
func (e *ItemsRepository) UpdateByGroup(ctx context.Context, groupID uuid.UUID, data ItemUpdate) (ItemOut, error) {
q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(groupID))).
SetName(data.Name).
SetDescription(data.Description).
SetLocationID(data.LocationID).
@@ -686,16 +688,16 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data
}
}
e.publishMutationEvent(GID)
e.publishMutationEvent(groupID)
return e.GetOne(ctx, data.ID)
}
func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, GID uuid.UUID) ([]uuid.UUID, error) {
func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, groupID uuid.UUID) ([]uuid.UUID, error) {
var ids []uuid.UUID
err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
item.Or(
item.ImportRefEQ(""),
item.ImportRefIsNil(),
@@ -710,11 +712,11 @@ func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, GID uuid.UUID
return ids, nil
}
func (e *ItemsRepository) Patch(ctx context.Context, GID, ID uuid.UUID, data ItemPatch) error {
func (e *ItemsRepository) Patch(ctx context.Context, groupID, itemID uuid.UUID, data ItemPatch) error {
q := e.db.Item.Update().
Where(
item.ID(ID),
item.HasGroupWith(group.ID(GID)),
item.ID(itemID),
item.HasGroupWith(group.ID(groupID)),
)
if data.ImportRef != nil {
@@ -725,11 +727,11 @@ func (e *ItemsRepository) Patch(ctx context.Context, GID, ID uuid.UUID, data Ite
q.SetQuantity(*data.Quantity)
}
e.publishMutationEvent(GID)
e.publishMutationEvent(groupID)
return q.Exec(ctx)
}
func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid.UUID, name string) ([]string, error) {
func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, groupID uuid.UUID, name string) ([]string, error) {
type st struct {
Value string `json:"text_value"`
}
@@ -738,7 +740,7 @@ func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid.
err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
).
QueryFields().
Where(
@@ -759,7 +761,7 @@ func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid.
return valueStrings, nil
}
func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.UUID) ([]string, error) {
func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, groupID uuid.UUID) ([]string, error) {
type st struct {
Name string `json:"name"`
}
@@ -768,7 +770,7 @@ func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.U
err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
).
QueryFields().
Unique(true).
@@ -792,13 +794,16 @@ func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.U
// This is designed to resolve a long-time bug that has since been fixed with the time selector on the
// frontend. This function is intended to be used as a one-time fix for existing databases and may be
// removed in the future.
func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID) (int, error) {
func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, groupID uuid.UUID) (int, error) {
q := e.db.Item.Query().Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
item.Or(
item.PurchaseTimeNotNil(),
item.PurchaseFromLT("0002-01-01"),
item.SoldTimeNotNil(),
item.SoldToLT("0002-01-01"),
item.WarrantyExpiresNotNil(),
item.WarrantyDetailsLT("0002-01-01"),
),
)
@@ -817,15 +822,36 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID)
updateQ := e.db.Item.Update().Where(item.ID(i.ID))
if !i.PurchaseTime.IsZero() {
updateQ.SetPurchaseTime(toDateOnly(i.PurchaseTime))
switch {
case i.PurchaseTime.Year() < 100:
updateQ.ClearPurchaseTime()
default:
updateQ.SetPurchaseTime(toDateOnly(i.PurchaseTime))
}
} else {
updateQ.ClearPurchaseTime()
}
if !i.SoldTime.IsZero() {
updateQ.SetSoldTime(toDateOnly(i.SoldTime))
switch {
case i.SoldTime.Year() < 100:
updateQ.ClearSoldTime()
default:
updateQ.SetSoldTime(toDateOnly(i.SoldTime))
}
} else {
updateQ.ClearSoldTime()
}
if !i.WarrantyExpires.IsZero() {
updateQ.SetWarrantyExpires(toDateOnly(i.WarrantyExpires))
switch {
case i.WarrantyExpires.Year() < 100:
updateQ.ClearWarrantyExpires()
default:
updateQ.SetWarrantyExpires(toDateOnly(i.WarrantyExpires))
}
} else {
updateQ.ClearWarrantyExpires()
}
_, err = updateQ.Save(ctx)
@@ -839,11 +865,11 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID)
return updated, nil
}
func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, GID uuid.UUID) (int, error) {
func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, groupID uuid.UUID) (int, error) {
// All items where there is no primary photo
itemIDs, err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(groupID)),
item.HasAttachmentsWith(
attachment.TypeEQ(attachment.TypePhoto),
attachment.Not(
@@ -877,7 +903,6 @@ func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, GID uuid.UUID) (
_, err = e.db.Attachment.UpdateOne(a).
SetPrimary(true).
Save(ctx)
if err != nil {
return updated, err
}

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