Compare commits

...
Sign in to create a new pull request.

392 commits

Author SHA1 Message Date
6e1e5a7280
backend: go get -u and mod tidy
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2025-04-12 21:34:25 -04:00
b5fbe698be
backend: go mod tidy
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2025-04-12 19:29:34 -04:00
14466cde2e
frontend: pnpm update
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2025-04-12 19:26:15 -04:00
90e495a748
pnpm: fix-lockfile
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2025-04-12 12:48:27 -04:00
e32ca9442d
Dockerfile: carry over local changes
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2025-04-12 09:57:36 -04:00
9895088668
Dockerfile: dos2unix
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2025-04-12 09:55:05 -04: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: e8449b3a7363a6cfd5bc6151609e6d2d94b4f7d8
2024-01-04 08:55:26 -09:00
zebrapurring
5e83b28ff5 fix: Render newlines in markdown fields (#682)
Co-authored-by: zebrapurring <>
Former-commit-id: b9183109f2c7ac309fd44073a1ee6f1f4b013ec8
2023-12-24 18:27:58 -09:00
Hayden
d759fad40c feat: add log statements + remove auto redirect (#671)
Former-commit-id: 3ee150e7d66d18c6369d171994932744a614a74f
2023-12-15 18:32:44 -09:00
Hayden
1cc9bf459a fix: ensure rows are closed (#670)
Former-commit-id: 85c8e4701606c7e4f1850414d467352f436a0e9e
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: 8538877f52b9aae43b38afb37e5c69d99cb4827c
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: 522943687ed606da921b3adfa4eada9ac86bfaad
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: ff2577a739289e815e1c64495e123a051f402996
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: 0bc3969c0cb51c15f7eda869b0f4228cc08e7b47
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: 8a85b189974d876fe4e48f0492fe12f8966e79c8
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: b5592167a260646662e19085e94dc46cd57cf908
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: 126c06a0fa182197084776e16ba4660d398f25dc
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: 014fdea4ff80cc74ac518b0331438bb8ec2dc2c8
2023-12-01 09:39:37 -09:00
Hayden
1b77b15db4 fix: ignore /api paths from service workers (#649)
Former-commit-id: f2363b85cb71e776d685527d10525b4e75e16883
2023-12-01 08:57:59 -09:00
Hayden
fc3bc3f010 fix: loading causing stale data to present in form(#650)
Former-commit-id: 4d9131ba7ea05e150b5c8a332ddf612d72f6055b
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: 321a83b6344461233979e4f45e60f3121b1ffcef
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: d1d98bcb067c6e510dd3ad0650a33b3daa8593d0
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: db00e7a268a39a9f8112e3b1b8c1f7b1f5384949
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: 7ec22ed9bd24dbebcc1e02c59e35ccf06ec6c764
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: 10c030a56b5ba9020b12b6b35f0c7ef737b43d6b
2023-11-30 14:25:22 -09:00
Hayden
d6b0062ae9 feat: set version flag (#632)
Former-commit-id: 9edbda3daa7df20c2ba1df1fbddf0ecabfa3ee92
2023-11-24 10:02:02 -09:00
Hayden
df26a3eeb7 fix: allow zeroing out asset ids (#624)
Former-commit-id: 8cc0f302919758044dc9ff4dc785c452364dc715
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: afbc6a49ac0a39684e42c95eba04517745b4ccf1
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: 2594d4cdb4dbbde4c84b43a507d3b8ec7b9eb68c
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: 2eafa8e72f9d6c8c56d088ad54bdef0e29ddfbc7
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: e65d44fa9eb7cbcba478790594be24f6b36c2bbf
2023-11-15 18:17:57 -09:00
Hayden
79be938531 feat: expose timeout variables (#622)
* expose timeout variables

* formatting

Former-commit-id: eeae790fe49b4a6be95b412ab5b3cf8968ec9cc1
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: a70ee33759712590d63842fe3c8662584b39776e
2023-11-15 18:15:31 -09:00
Hayden
eb9175ab27 fix: use theme aware background color (#619)
Former-commit-id: 80e20713008e34fbf0e66b05d49a1dee262ccd9c
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: db27d34b4f0d118e417bd5b399b926e6eeada2b2
2023-11-15 17:58:30 -09:00
Owen Valentine
ab98870350 feat : Validate bark, ntfy, generic+ shortcut (#591)
Former-commit-id: da22074ed34da7696b1281bd10c61f1dad2786f0
2023-11-15 17:53:06 -09:00
Hayden
13c437c418 fix: ensure urls isn't encoded (#618)
Former-commit-id: 51ba15f84c47026c09c8193767a1492af4ee44f7
2023-11-15 17:51:19 -09:00
Hayden
ba2e66a014 fix: filepath sep on windows (#615)
Former-commit-id: b408318acbc2b1154d710f559d7572c110a94445
2023-11-15 17:43:44 -09:00
Hayden
1adf24e109 chore: bump all go dependencies (#614)
* bump all

* code-generation

Former-commit-id: c0e8e340655860f5bd3e92397a10ef844320eb11
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: 4738a9b131e75479b30bda3b9967329268f69685
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: b08e52104c70ee5cbf60bbffd86ecca4a5d5dcd0
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: 3e2ab29054b48ef48baf404c64eec2f26d2bb28f
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: e5f66d99bc0523db58c30e49a17a4323f2030d3a
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
renovate[bot]
feab9f4c46
chore(deps): update dependency @vite-pwa/nuxt to ^0.1.0 (#474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-22 19:46:08 -08:00
renovate[bot]
fe5622d62a
fix(deps): update module golang.org/x/crypto to v0.11.0 (#495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-22 19:45:56 -08:00
renovate[bot]
e759f2817e
chore(deps): update dependency nuxt to v3.6.5 (#503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-22 19:45:35 -08:00
renovate[bot]
60cc5c2710
chore(deps): update dependency mkdocs-material to v9.1.19 (#497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-22 19:44:19 -08:00
renovate[bot]
25ccd678c9
chore(deps): update typescript-eslint monorepo to v6 (#500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-20 11:13:42 -08:00
Pranshu Agrawal
a77b4cbe71
update "h3" verison to 1.7.1 (#502) 2023-07-20 11:13:18 -08:00
renovate[bot]
aae32b0d74
fix(deps): update module github.com/go-chi/chi/v5 to v5.0.10 (#493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-18 12:08:23 -08:00
renovate[bot]
a94b43a19e
fix(deps): update module github.com/ardanlabs/conf/v3 to v3.1.6 (#492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-18 12:08:05 -08:00
renovate[bot]
9a4c2df552
chore(deps): update dependency vitest to ^0.33.0 (#494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-18 12:07:55 -08:00
renovate[bot]
d5b89a755e
chore(deps): update actions/setup-go action to v4 (#496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-18 12:07:45 -08:00
renovate[bot]
a9acf62d93
chore(deps): update dependency mkdocs-material to v9.1.18 (#491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-16 17:51:13 -08:00
renovate[bot]
c896e198dd
fix(deps): update module github.com/swaggo/http-swagger to v2 (#490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-16 17:39:42 -08:00
Cappy-Bara
c538518b4b
fix: add parseDependency to swag (#486) 2023-07-16 17:37:56 -08:00
renovate[bot]
f66d14eeea
fix(deps): update module github.com/stretchr/testify to v1.8.4 (#468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-16 17:34:51 -08:00
renovate[bot]
bc8feac83c
chore(deps): update dependency nuxt to v3.6.3 (#350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-16 17:34:23 -08:00
renovate[bot]
40a98bcf30
fix(deps): update module modernc.org/sqlite to v1.24.0 (#437)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-16 17:34:00 -08:00
renovate[bot]
045e91d9ac
fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.17 (#470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-16 17:33:35 -08:00
renovate[bot]
a80ab0f3e9
fix(deps): update module github.com/go-playground/validator/v10 to v10.14.1 (#478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-16 17:33:22 -08:00
renovate[bot]
e5d209d407
chore(deps): update dependency mkdocs-material to v9.1.15 (#467)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-02 13:57:09 -08:00
Hayden
ef1531e561
feat: easily increment quantity (#473)
* fix vue version issue

* new patch API endpoint

* doc-gen

* new API class method for patch operations

* add quantity patch UI support

* fix typegen errors

* fix ts errors
2023-06-02 13:56:40 -08:00
renovate[bot]
4dd036abb2
chore(deps): update dependency @vite-pwa/nuxt to ^0.0.9 (#453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-28 10:59:57 -08:00
Hayden
81e909ccfb
chore: tidy (#466) 2023-05-28 10:59:44 -08:00
Hayden
0cb9d2a8e4
bump nuxt + fix CookieRef (#465) 2023-05-28 09:51:23 -08:00
renovate[bot]
cb16c0e829
fix(deps): update module github.com/go-playground/validator/v10 to v10.14.0 (#462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-23 10:43:42 -08:00
renovate[bot]
9e067ee230
fix(deps): update module github.com/stretchr/testify to v1.8.3 (#460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-23 10:43:34 -08:00
renovate[bot]
8b53d40a2a
chore(deps): update dependency mkdocs-material to v9.1.14 (#461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-23 10:42:49 -08:00
renovate[bot]
4c0ad7a5d8
chore(deps): update dependency mkdocs-material to v9.1.13 (#457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-16 18:24:19 -08:00
D M
66e25ba068
feat: Low-Privileged and Distroless Docker Image (#372)
* feat: use distroless image and non-root user

* fix: remove conflicts after merge

* chore: Commen the Dockerfile

* chore: Update documentation to reflect image changes

* Split docker build in latest and latest-rootless

One more job added to the publish Github Action, to build and push TAG-rootless
images.

* fix: add missing workflow

* feat: update documentation about double tags

* feat: update readme with double tags

---------

Co-authored-by: daniele <daniele@coolbyte.eu>
2023-05-13 10:38:57 -08:00
renovate[bot]
56c98e6e3a
chore(deps): update dependency @faker-js/faker to v8 (#449)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 10:34:57 -08:00
renovate[bot]
01f305a98e
fix(deps): update github.com/gocarina/gocsv digest to 7f30c79 (#448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 10:34:45 -08:00
renovate[bot]
e14cdaccdd
fix(deps): update module golang.org/x/crypto to v0.9.0 (#447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 10:34:33 -08:00
renovate[bot]
4ece25b58d
chore(deps): update dependency mkdocs-material to v9.1.12 (#444)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 10:34:23 -08:00
renovate[bot]
c1957bb927
chore(deps): update dependency vitest to ^0.31.0 (#442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-06 10:00:40 -08:00
renovate[bot]
636ca155e5
fix(deps): update module github.com/go-playground/validator/v10 to v10.13.0 (#438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-06 10:00:33 -08:00
renovate[bot]
17a5b43609
chore(deps): update dependency mkdocs-material to v9.1.9 (#440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-02 17:09:15 -08:00
renovate[bot]
b2b3ccf923
chore(deps): update dependency mkdocs-material to v9.1.8 (#435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-24 23:08:32 -08:00
renovate[bot]
2272c7eb6b
fix(deps): update module modernc.org/sqlite to v1.22.0 (#427)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-24 18:30:53 -08:00
renovate[bot]
181c324dd4
chore(deps): update dependency mkdocs-material to v9.1.7 (#432)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-24 18:30:41 -08:00
renovate[bot]
89912b18d7
fix(deps): update dependency @vueuse/router to v10 (#418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-21 22:54:09 -08:00
renovate[bot]
85f2af4bc3
fix(deps): update module github.com/swaggo/swag to v1.16.1 (#426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-21 22:53:29 -08:00
Hayden
21ad5a32c1
fix: add generic shoutrrr prefix to validators(#422) 2023-04-16 11:21:33 -08:00
renovate[bot]
4b51a4ad9a
fix(deps): update module ariga.io/atlas to v0.10.1 (#395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 20:37:57 -08:00
renovate[bot]
dd7e634b69
fix(deps): update dependency @vueuse/nuxt to v10 (#415)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 20:37:00 -08:00
renovate[bot]
351ec64bbc
fix(deps): update module github.com/rs/zerolog to v1.29.1 (#414)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 20:36:14 -08:00
renovate[bot]
3a758e012f
chore(deps): update dependency vitest to ^0.30.0 (#403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 20:35:55 -08:00
renovate[bot]
c16084d99f
chore(deps): update dependency mkdocs-material to v9.1.6 (#401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 20:35:42 -08:00
renovate[bot]
5591267124
fix(deps): update module golang.org/x/crypto to v0.8.0 (#400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 20:35:27 -08:00
renovate[bot]
d46c16f01f
fix(deps): update github.com/gocarina/gocsv digest to 6445c2b (#398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 20:35:18 -08:00
zodac
18c22e8a68
Fixing minor typos (#368)
* Adding fullstops for consistency

* Fixing typos

* Fixing eslints
2023-04-14 20:34:40 -08:00
Hayden
64b3ac3e94
change safeserve -> httpkit (#405) 2023-04-09 10:39:43 -08:00
verybadsoldier
c36b9dcf5d
update go version in devcontainer to 1.20 to match version in go.mod (#402) 2023-04-09 10:38:17 -08:00
Hayden
d3b6c93b63
use ref_name 2023-04-03 14:21:54 -08:00
Hayden
3b862e36c8
fix CI 2023-04-03 14:18:08 -08:00
Hayden
f3bb86d905
change publish workflow (#390) 2023-04-03 14:10:45 -08:00
Hayden
4dd925caf0
fix: other minor fixes (#388)
* remove overflow-hidden on when no collapsed

* fix recently added on homescreen

* fix delete account formatting

* add manufacturer to search

* move nav button to left
2023-04-01 22:01:21 -08:00
Hayden
ced5aef6d1
fix: export child relationships (#385)
* tidy

* ensure export contains locations path

* update pnpm lock file

* fix swagger stuff

* code gen

* fix linter issue

* fix reverse order bug in test
2023-04-01 15:10:27 -08:00
Hayden
6a853c07a0
fix: various minor bugs (#384)
* fix insufficiently large max height for cards

* fix listener for resetItemDateTimes

* support YYYY/MM/DD format for imports

* fix columns in docs

* use comma deliminator
2023-04-01 14:07:44 -08:00
renovate[bot]
f0b9a0fce4
chore(deps): update dependency mkdocs-material to v9.1.5 (#382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 12:22:34 -08:00
renovate[bot]
00f09fec2f
fix(deps): update module modernc.org/sqlite to v1.21.1 (#381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-28 19:49:16 -08:00
renovate[bot]
6e1863b515
fix(deps): update module github.com/hay-kot/safeserve to v0.0.2 (#378)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-28 19:49:07 -08:00
renovate[bot]
dfe2084c74
fix(deps): update github.com/gocarina/gocsv digest to 9a18a84 (#375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-28 19:48:59 -08:00
renovate[bot]
0825f055a7
fix(deps): update module github.com/swaggo/swag to v1.8.12 (#380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-28 19:48:49 -08:00
Hayden
e1e04d49aa
update git ignore 2023-03-25 11:20:38 -08:00
Hayden
9020587c9e
set working directory 2023-03-25 11:17:07 -08:00
Hayden
bd0db1ea37
remove go tidy 2023-03-25 11:13:23 -08:00
Hayden
d2985ff72c
go tidy 2023-03-25 11:12:48 -08:00
Hayden
8c57ff841e
fix: redirect issues for authorized users (#374) 2023-03-25 11:07:22 -08:00
renovate[bot]
0264bfb8c1
chore(deps): update dependency mkdocs-material to v9.1.4 (#371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 08:03:56 -08:00
Hayden
5dd6844536
feat: change shit to things (#369) 2023-03-23 19:10:19 -08:00
Hayden
0f8db862b4
feat: pwa support (#366)
* add PWA support

* fix broken URLs for query

* remove unused variable

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

* use singleton context to manage user state

* implement remember-me functionality

* fix errors

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

* update image path

* spelling

* set working dir

* change main.go

* remove unused field

* drop cgo requirement

* remove unused workflow step

* generate code

* drop cgo from docker file

* update publish workflow

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

* Updating frontend

* Sorting alphabetically

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

* ensure exhaustive checks

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

* add new background service to manage scheduled notifications

* update docs

* remove old js reference

* closes #278

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

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

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

* enable dumping stack traces for errors

* log request start and stop

* set zerolog stack handler

* fix routes function

* refactor context adapters to use requests directly

* change some method signatures to support GID

* start requiring validation tags

* first pass on updating handlers to use adapters

* add errs package

* code gen

* tidy

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

* wip: shoutrrr wrapper (may remove)

* update schema files

* gen: ent code

* gen: migrations

* go mod tidy

* add group_id to notifier

* db migration

* new mapper helpers

* notifier repo

* introduce experimental adapter pattern for hdlrs

* refactor adapters to fit more common use cases

* new routes for notifiers

* update errors to fix validation panic

* go tidy

* reverse checkbox label display

* wip: notifiers UI

* use badges instead of text

* improve documentation

* add scaffold schema reference

* remove notifier service

* refactor schema folder

* support group edges via scaffold

* delete test file

* include link to API docs

* audit and update documentation + improve format

* refactor schema edges

* refactor

* add custom validator

* set validate + order fields by name

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

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

* add romanian lei

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

* refactoring

* fix failing JS tests

* update import docs

* fix import headers

* fix column headers

* update refs on import

* remove demo status

* finnnneeeee

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

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

* accept cookies for authentication

* remove auth store

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

* add homebox to know words

* formatting

* bump deps

* generate db models

* ts errors

* drop id

* fix accessor

* drop unused time field

* change CI

* add expected error

* add type check

* resolve serveral type errors

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

* code gen

* use env for troubleshooting

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

* fix user registration bug

* remove sanity check

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

* API route

* code gen

* get tsv export from tools page

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

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

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

* expand query to support by Field and By AID search

* type generation

* new API callers

* rework search to support field queries

* improve unnecessary data fetches

* clear stores on logout

* change verbage

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

* support new location selector in create item

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

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

* test fixes

* initial tree location elements

* locations tree page

* update meta-data

* code-gen

* store item display preferences

* introduce basic table/card view elements

* codegen

* set parent location during location creation

* add item support for tree query

* refactor tree view

* wip: location selector improvements

* type gen

* rename items -> search

* remove various log statements

* fix markdown rendering for description

* update location selectors

* fix tests

* fix currency tests

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

* fix tailwind auto-complete

* force lowercase buttons

* add title and change style for items page

* add copy button support for item details

* empty state for log

* fix duplicate padding

* add option for create without closing the current dialog.

* hide purchase price is not set

* invert toggle for edit mode

* update styles on item cards

* add edit support for maintenance logs
2023-01-21 21:15:23 -09:00
Hayden
c19fe94c08
feat: QR Codes (#226)
* code gen updates

* qrcode support

* remove opacity on toast

* update item view to use tab-like pages

* adjust view for cards

* fix old API calls for ioutils

* move embed

* extract QR code

* add docs for QR codes

* add QR code
2023-01-18 20:44:06 -09:00
renovate[bot]
f532b39c46
chore(deps): update docker/setup-qemu-action action to v2 (#219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 13:56:03 -09:00
renovate[bot]
243742a75c
chore(deps): update docker/setup-buildx-action action to v2 (#218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 13:55:56 -09:00
renovate[bot]
2b7c1fa429
chore(deps): update dependency @nuxtjs/eslint-config-typescript to v12 (#217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 13:55:48 -09:00
renovate[bot]
125b8f5ecd
chore(deps): update actions/setup-go action to v3 (#216)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 13:55:40 -09:00
renovate[bot]
580de45cec
chore(deps): update actions/checkout action to v3 (#215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 13:55:32 -09:00
renovate[bot]
da52b4ec64
chore(deps): update dependency vitest to ^0.27.0 (#214)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 13:55:23 -09:00
renovate[bot]
5b0dba854d
fix(deps): update dependency mkdocs-material to v9 (#222)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 13:54:33 -09:00
renovate[bot]
984c1a08ac
fix(deps): update module ariga.io/atlas to v0.9.0 (#213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-14 09:51:47 -09:00
renovate[bot]
8419a17109
fix(deps): update module entgo.io/ent to v0.11.5 (#211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-14 09:40:20 -09:00
renovate[bot]
f3406eec19
chore(deps): update pnpm/action-setup action to v2.2.4 (#210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-14 09:40:14 -09:00
renovate[bot]
3ed07a909f
Add renovate.json (#209)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-14 09:28:52 -09:00
Hayden
07441eec8e
feat: add lookup by asset ID (#208)
* add asset id redirecting

* dev env changes

* suggested changes from PR

* remove unnecessary proxy from nuxt config

* fix formatting

* change directory reference

* fix API key storage

* use /a/{id} as redirect

* run generators

* remove dependabot

Co-authored-by: Bradley Nelson <bradley@nel.family>
Co-authored-by: Bradley Nelson <BCNelson@users.noreply.github.com>
2023-01-14 09:24:11 -09:00
dependabot[bot]
c78f10ba5d
fix(deps): bump golang.org/x/crypto from 0.4.0 to 0.5.0 in /backend (#199)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-08 10:02:14 -09:00
Hayden
490a0ece86
chore: rewrite python script in go (#201) 2023-01-06 22:15:59 -09:00
Hayden
891d41b75f
feat: new-card-design (#196)
* card option 1

* UI updates for item card

* fix test error

* fix pagination issues on backend

* add integer support

* remove date from cards

* implement pagination for search page

* resolve search state problems

* other fixes

* fix broken datetime

* attempt to fix scroll behavior
2023-01-01 12:50:48 -09:00
Hayden
58d6f9a28c
Fix/mobile-layouts (#192)
* partial fix for location card spacing

* update header on mobile
2022-12-29 20:18:49 -09:00
Hayden
6a8a25e3f8
feat: new dashboard implementation (#168)
* wip: charts.js experimental work

* update lock file

* wip: frontend redesign

* wip: more UI fixes for consistency across themes

* cleanup

* improve UI log

* style updates

* fix lint errors
2022-12-29 16:19:15 -09:00
dependabot[bot]
a3954dab0f
fix(deps): bump golang.org/x/crypto from 0.3.0 to 0.4.0 in /backend (#174)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-15 22:36:22 -09:00
dependabot[bot]
08d483d907
fix(deps): bump github.com/go-chi/chi/v5 from 5.0.7 to 5.0.8 in /backend (#172)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.7 to 5.0.8.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.7...v5.0.8)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-15 22:36:12 -09:00
dependabot[bot]
a53f89ceac
fix(deps): bump github.com/swaggo/swag from 1.8.8 to 1.8.9 in /backend (#178)
Bumps [github.com/swaggo/swag](https://github.com/swaggo/swag) from 1.8.8 to 1.8.9.
- [Release notes](https://github.com/swaggo/swag/releases)
- [Changelog](https://github.com/swaggo/swag/blob/master/.goreleaser.yml)
- [Commits](https://github.com/swaggo/swag/compare/v1.8.8...v1.8.9)

---
updated-dependencies:
- dependency-name: github.com/swaggo/swag
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-15 18:21:12 -09:00
Hayden
5bbb969763
feat: maintenance log (#170)
* remove repo for document tokens

* remove schema for doc tokens

* fix id template and generate cmd

* schema updates

* code gen

* bump dependencies

* fix broken migrations + add maintenance entry type

* spelling

* remove debug logger

* implement repository layer

* routes

* API client

* wip: maintenance log

* remove depreciated call
2022-12-09 20:57:57 -09:00
Hayden
d6da63187b
feat: new homepage statistic API's (#167)
* add date format and orDefault helpers

* introduce new statistics calculations queries

* rework statistics endpoints

* code generation

* fix styles on photo card

* label and location aggregation endpoints

* code-gen

* cleanup parser and defaults

* remove debug point

* setup E2E Testing

* linters

* formatting

* fmt plus name support on time series data

* code gen
2022-12-05 12:36:32 -09:00
Hayden
de419dc37d
feat: auth-roles, image-gallery, click-to-open (#166)
* schema changes

* db generate

* db migration

* add role based middleware

* implement attachment token access

* generate docs

* implement role based auth

* replace attachment specific tokens with gen token

* run linter

* cleanup temporary token implementation
2022-12-03 10:55:00 -09:00
Hayden
974d6914a2
feat: markdown support (#165)
* initial markdown support via markdown-it

* sanitize markup

* remove pre-padding

* fix linter errors
2022-12-02 16:12:32 -09:00
Steven Nguyen
d2aa022347
Fix typo in documentation (import-csv.md) (#163)
* Fix typo in .csv header snippet

Import RefLocation -> ImportRef Location

* Convert space to tab in header snippet
2022-12-02 15:02:59 -09:00
Hayden
6af048dc93
feat: present loc/labels based on route (#162) 2022-12-01 18:21:49 -09:00
Hayden
73c42f4784
fix: assign extension if not set (#161) 2022-12-01 18:10:50 -09:00
Hayden
f42a917390
feat: add tsv support for import files (#160)
* feat: add tsv support for import files

* add note in docs
2022-12-01 18:06:47 -09:00
Hayden
f149c3e4ab
feat: include notes in search (#159) 2022-12-01 17:32:11 -09:00
Hayden
1dc1ee54e2
feat: chinese-currency (#158)
* run updated code gen

* feat: add RMB currency support
2022-12-01 16:45:28 -09:00
dependabot[bot]
e8f215ce34
fix(deps): bump github.com/swaggo/swag from 1.8.7 to 1.8.8 in /backend (#153)
Bumps [github.com/swaggo/swag](https://github.com/swaggo/swag) from 1.8.7 to 1.8.8.
- [Release notes](https://github.com/swaggo/swag/releases)
- [Changelog](https://github.com/swaggo/swag/blob/master/.goreleaser.yml)
- [Commits](https://github.com/swaggo/swag/compare/v1.8.7...v1.8.8)

---
updated-dependencies:
- dependency-name: github.com/swaggo/swag
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-24 14:29:43 -09:00
dependabot[bot]
1aafbcd201
fix(deps): bump entgo.io/ent from 0.11.3 to 0.11.4 in /backend (#117)
Bumps [entgo.io/ent](https://github.com/ent/ent) from 0.11.3 to 0.11.4.
- [Release notes](https://github.com/ent/ent/releases)
- [Commits](https://github.com/ent/ent/compare/v0.11.3...v0.11.4)

---
updated-dependencies:
- dependency-name: entgo.io/ent
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-24 14:29:36 -09:00
Hayden
6dc2ae1bea
feat: asset tags/ids (#142)
* add schema

* run db migration

* bulk seed asset IDs

* breaking: update runtime options

* conditionally increment asset IDs

* update API endpoints

* fix import asset id assignment

* refactor display + marshal/unmarshal

* add docs page

* add to form field

* hide 000-000 values

* update ENV vars
2022-11-13 14:17:55 -09:00
Hayden
976f68252d
feat: add INR currency and sort currencies (#141) 2022-11-12 14:54:24 -09:00
Hayden
8e1947d971
fix: conditionally filter parent locations (#133) 2022-11-02 11:54:43 -08:00
Hayden
fbcbde836a
hotfix: search default 2022-11-01 21:59:57 -08:00
Hayden
b7aacb3cde
feat: encode search into url (#131)
* route query helper

* encode search parameters into url
2022-11-01 21:58:46 -08:00
Hayden
b6b2a2d889
feat: add currencies (NOK, SEK, DKK) (#128) 2022-11-01 15:10:48 -08:00
Hayden
592d4eda55
feat: filter items with parents on homepage (#127) 2022-11-01 15:10:30 -08:00
Hayden
2fb5a437a2
feat: add additional currencies (#125)
Add additional currencies and ensure Frontend/Backend currencies are synched via testing
2022-11-01 14:16:22 -08:00
Hayden
7e0f1fac23
feat: group statistics endpoint (#123)
* group statistics endpoint

* remove item store

* return possible errors

* add statistics tests
2022-11-01 13:58:05 -08:00
Hayden
a886fa86ca
feat: add archive item options (#122)
Add archive option feature. Archived items can only be seen on the items page when including archived is selected. Archived items are excluded from the count and from other views
2022-10-31 23:30:42 -08:00
Hayden
c722495fdd
feat: redirect to item on create (#121)
* redirect on create

* change to text area
2022-10-31 19:07:01 -08:00
Hayden
4a9d21d604
fix: time-format-inconsistency (#120)
* fix off by one date display

* display dates in consistent format

* use token or ci
2022-10-31 18:43:30 -08:00
Hayden
cd82fe0d89
refactor: remove empty services (#116)
* remove empty services

* remove old factory

* remove old static files

* cleanup more duplicate service code

* file/folder reorg
2022-10-29 20:05:38 -08:00
Hayden
6529549289
refactor: http interfaces (#114)
* implement custom http handler interface

* implement trace_id

* normalize http method spacing for consistent logs

* fix failing test

* fix linter errors

* cleanup old dead code

* more route cleanup

* cleanup some inconsistent errors

* update and generate code

* make taskfile more consistent

* update task calls

* run tidy

* drop `@` tag for version

* use relative paths

* tidy

* fix auto-setting variables

* update build paths

* add contributing guide

* tidy
2022-10-29 18:15:35 -08:00
dependabot[bot]
e2d93f8523
fix(deps): bump github.com/mattn/go-sqlite3 in /backend (#113)
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.15 to 1.14.16.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.15...v1.14.16)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-29 18:11:23 -08:00
Hayden
82269e8a95 clean logs 2022-10-25 11:24:19 -08:00
Hayden
4aee60c242 update frontend output 2022-10-25 09:17:31 -08:00
Hayden
d151d42081
feat: debug-endpoints (#110)
* reorg + pprof endpoints

* fix spacing issue

* fix generation directory
2022-10-24 18:24:18 -08:00
Hayden
a4b4fe3454
feat: allow nested relationships for locations and items (#102)
Basic implementation that allows organizing Locations and Items within each other.
2022-10-23 20:54:39 -08:00
dependabot[bot]
fe6cd431a6
fix(deps): bump github.com/stretchr/testify in /backend (#107)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-23 20:40:21 -08:00
andrew-busbee
1f0c8d09c2
docs: typo quick-start.md (#103)
Small typo
2022-10-20 22:39:50 -08:00
Hayden
2d34557f69
feat: link implementation (#100)
* link implementation

* add docs for links

* use btn instead of badge
2022-10-19 21:31:08 -08:00
dependabot[bot]
97a34475c8
fix(deps): bump github.com/swaggo/swag from 1.8.6 to 1.8.7 in /backend (#97)
Bumps [github.com/swaggo/swag](https://github.com/swaggo/swag) from 1.8.6 to 1.8.7.
- [Release notes](https://github.com/swaggo/swag/releases)
- [Changelog](https://github.com/swaggo/swag/blob/master/.goreleaser.yml)
- [Commits](https://github.com/swaggo/swag/compare/v1.8.6...v1.8.7)

---
updated-dependencies:
- dependency-name: github.com/swaggo/swag
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-18 21:47:46 -08:00
Hayden
18488f5b15
refactor: cleanup-api-functions (#94)
* cleanup items endpoints

* refactor group routes

* refactor labels routes

* remove old partial

* refactor location routes

* formatting

* update names

* cleanup func

* speedup test runner with disable hasher

* remove duplicate code
2022-10-16 18:50:44 -08:00
Hayden
434f1fa411 add support for custom text fields 2022-10-15 21:41:27 -08:00
Hayden
57f9372e49
feat: add receipt support for attachments (#89)
* add receipt support for attachments

* fix show logic
2022-10-15 19:45:36 -08:00
Hayden
dbaaf4ad0a
fix import bug and add ref support (#88)
* fix import bug and add ref support

* fix calls

* add docs
2022-10-15 17:46:57 -08:00
Hayden
5596740cd2
fix: selector value binding (#87) 2022-10-15 13:56:08 -08:00
Hayden
bb86a51b05
feat: order labels and locations by name (#86)
* order labels and locations by name

* sort items
2022-10-15 13:29:33 -08:00
Hayden
72bdf524c2
chore: init dev container (#85) 2022-10-15 12:56:58 -08:00
Hayden
461be2afca
feat: currency selection support (#72)
* initial UI for currency selection

* add task to purge invitation tokens

* group API contracts

* fix type import

* use auth middleware

* add currency setting support (UI)

* use group settings for format currency

* fix casing
2022-10-15 12:15:55 -08:00
Hayden
1cc38d6a5c
Create FUNDING.yml 2022-10-15 11:24:09 -08:00
Motordom
50bd2ab86e
fix: consistency changes to item pages (#82)
Changes to the item index.vue and edit.vue pages to resolve spelling differences between the two
2022-10-14 21:03:46 -08:00
Motordom
5adb8fbad7
chore: update issue templates (#78)
Update capitalisation on descriptions, and added corresponding labels.
2022-10-14 21:02:34 -08:00
Hayden
dea2dcfde8
feat: allow disable registration (#71) 2022-10-14 14:02:16 -08:00
Hayden
ba8367f637
fix: compose example (#70) 2022-10-14 13:40:27 -08:00
Motordom
b87cdc8164
fix(s): spelling mistakes, and external link handling on login page (#65)
* Fix spelling mistake

Fix spelling mistake, changed 'Mode Number' to 'Model Number'

* Update external links on login page

Updated the external links on the login page for Discord and application docs to open in new tabs/windows.

* Fix spelling mistake on items page

Fix spelling mistake, changed second incorrect occurrence of 'Labels' to 'Locations'
2022-10-14 08:30:23 -08:00
Hayden
0f51e51f63 set fly.io build args 2022-10-13 17:12:35 -08:00
Hayden
1279028d07 fix build injection 2022-10-13 17:01:18 -08:00
Hayden
84bf67079b
feat: show app version (#60)
* add version and build to homepage view

* add version to profile
2022-10-13 16:49:29 -08:00
Hayden
889197994b
fix: auto-select bug (#59) 2022-10-13 16:45:18 -08:00
Hayden
ae73b194c4
fix: block self-delete on demo site (#57) 2022-10-13 09:37:29 -08:00
Hayden
30014a77ca
feat: expanded search for items (#46)
* expanded search for items

* range domain from email to example

* implement pagination for items
2022-10-12 21:13:07 -08:00
Hayden
1b20a69c5e
fix: select-first to update on items change (#45) 2022-10-12 14:07:22 -08:00
Hayden
a8e1d2c447
fix: move datepicker buttons to bottom (#44) 2022-10-12 14:07:11 -08:00
495 changed files with 64793 additions and 28373 deletions

5
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,5 @@
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT=16-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin

View file

@ -0,0 +1,40 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/typescript-node
{
"name": "Node.js & TypeScript",
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"args": {
"VARIANT": "18-bullseye"
}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"dbaeumer.vscode-eslint"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
7745,
3000
],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "go install github.com/go-task/task/v3/cmd/task@latest && npm install -g pnpm && task setup",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node",
"features": {
"golang": "1.21"
}
}

View file

@ -22,3 +22,4 @@
**/secrets.dev.yaml
**/values.dev.yaml
README.md
!Dockerfile.rootless

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: [hay-kot]

View file

@ -1,6 +1,7 @@
---
name: "Bug Report"
description: "submit a bug report for the current release"
description: "Submit a bug report for the current release"
labels: ["bug"]
body:
- type: checkboxes
id: checks

View file

@ -1,6 +1,7 @@
---
name: "Feature Request"
description: "submit a feature request for the current release"
description: "Submit a feature request for the current release"
labels: ["feature-request"]
body:
- type: textarea
id: problem-statement

View file

@ -1,31 +0,0 @@
version: 2
updates:
# Fetch and update latest `npm` packages
- package-ecosystem: npm
directory: "/frontend"
schedule:
interval: daily
time: "00:00"
open-pull-requests-limit: 10
reviewers:
- hay-kot
assignees:
- hay-kot
commit-message:
prefix: fix
prefix-development: chore
include: scope
- package-ecosystem: gomod
directory: backend
schedule:
interval: daily
time: "00:00"
open-pull-requests-limit: 10
reviewers:
- hay-kot
assignees:
- hay-kot
commit-message:
prefix: fix
prefix-development: chore
include: scope

View file

@ -7,18 +7,20 @@ jobs:
Go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: "1.21"
- name: Install Task
uses: arduino/setup-task@v1
with:
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
@ -28,7 +30,7 @@ jobs:
args: --timeout=6m
- name: Build API
run: task api:build
run: task go:build
- name: Test
run: task api:coverage
run: task go:coverage

View file

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

View file

@ -20,22 +20,22 @@ jobs:
name: "Publish Homebox"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: "1.20"
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt:latest
platforms: all
- name: install buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
with:
install: true
@ -44,7 +44,7 @@ jobs:
env:
CR_PAT: ${{ secrets.GH_TOKEN }}
- name: build nightly the image
- name: build nightly image
if: ${{ inputs.release == false }}
run: |
docker build --push --no-cache \
@ -53,6 +53,16 @@ jobs:
--build-arg=BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
- name: build nightly-rootless image
if: ${{ inputs.release == false }}
run: |
docker build --push --no-cache \
--tag=ghcr.io/hay-kot/homebox:${{ inputs.tag }}-rootless \
--build-arg=COMMIT=$(git rev-parse HEAD) \
--build-arg=BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--file Dockerfile.rootless \
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
- name: build release tagged the image
if: ${{ inputs.release == true }}
run: |
@ -60,6 +70,20 @@ jobs:
--tag ghcr.io/hay-kot/homebox:nightly \
--tag ghcr.io/hay-kot/homebox:latest \
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }} \
--build-arg VERSION=${{ inputs.tag }} \
--build-arg COMMIT=$(git rev-parse HEAD) \
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--platform linux/amd64,linux/arm64,linux/arm/v7 .
- name: build release tagged the rootless image
if: ${{ inputs.release == true }}
run: |
docker build --push --no-cache \
--tag ghcr.io/hay-kot/homebox:nightly-rootless \
--tag ghcr.io/hay-kot/homebox:latest-rootless \
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }}-rootless \
--build-arg VERSION=${{ inputs.tag }} \
--build-arg COMMIT=$(git rev-parse HEAD) \
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
--file Dockerfile.rootless .

View file

@ -1,73 +1,29 @@
name: Build Nightly
name: Publish Dockers
on:
push:
branches:
- main
release:
types:
- published
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
backend-tests:
name: "Backend Server Tests"
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
frontend-tests:
name: "Frontend and End-to-End Tests"
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
deploy:
name: "Deploy Nightly to Fly.io"
runs-on: ubuntu-latest
needs:
- backend-tests
- frontend-tests
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
publish-nightly:
name: "Publish Nightly"
if: github.event_name != 'release'
needs:
- backend-tests
- frontend-tests
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
with:
tag: nightly
secrets:
GH_TOKEN: ${{ secrets.CR_PAT }}
publish-tag:
name: "Publish Tag"
if: github.event_name == 'release'
needs:
- backend-tests
- frontend-tests
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
with:
release: true
tag: ${{ github.event.release.tag_name }}
secrets:
GH_TOKEN: ${{ secrets.CR_PAT }}
deploy-docs:
name: Deploy docs
needs:
- publish-tag
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG_FILE: docs/mkdocs.yml
EXTRA_PACKAGES: build-base

View file

@ -8,8 +8,8 @@ on:
jobs:
backend-tests:
name: "Backend Server Tests"
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
uses: ./.github/workflows/partial-backend.yaml
frontend-tests:
name: "Frontend and End-to-End Tests"
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
uses: ./.github/workflows/partial-frontend.yaml

77
.github/workflows/tag.yaml vendored Normal file
View file

@ -0,0 +1,77 @@
name: Publish Release
on:
push:
tags:
- v*
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
backend-tests:
name: "Backend Server Tests"
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
frontend-tests:
name: "Frontend and End-to-End Tests"
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
goreleaser:
name: goreleaser
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
- uses: pnpm/action-setup@v2
with:
version: 7.30.1
- name: Build Frontend and Copy to Backend
working-directory: frontend
run: |
pnpm install --shamefully-hoist
pnpm run build
cp -r ./.output/public ../backend/app/api/static/
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
workdir: "backend"
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-tag:
name: "Publish Tag"
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
with:
release: true
tag: ${{ github.ref_name }}
secrets:
GH_TOKEN: ${{ secrets.CR_PAT }}
deploy-docs:
name: Deploy docs
needs:
- publish-tag
- goreleaser
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v4
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG_FILE: docs/mkdocs.yml
EXTRA_PACKAGES: build-base

12
.gitignore vendored
View file

@ -3,7 +3,6 @@ backend/.data/*
config.yml
homebox.db
.idea
.DS_Store
test-mailer.json
node_modules
@ -32,6 +31,7 @@ node_modules
go.work
.task/
backend/.env
build/*
# Output Directory for Nuxt/Frontend during build step
backend/app/api/public/*
@ -45,3 +45,13 @@ node_modules
.output
.env
dist
.pnpm-store
backend/app/api/app
backend/app/api/__debug_bin
dist/
# Nuxt Publish Dir
backend/app/api/static/public/*
!backend/app/api/static/public/.gitkeep
backend/api

View file

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

View file

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

47
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,47 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"compounds": [
{
"name": "Full Stack",
"configurations": ["Launch Backend", "Launch Frontend"],
"stopAll": true
}
],
"configurations": [
{
"name": "Launch Backend",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/backend/app/api/",
"args": [],
"env": {
"HBOX_DEMO": "true",
"HBOX_LOG_LEVEL": "debug",
"HBOX_DEBUG_ENABLED": "true",
"HBOX_STORAGE_DATA": "${workspaceRoot}/backend/.data",
"HBOX_STORAGE_SQLITE_URL": "${workspaceRoot}/backend/.data/homebox.db?_fk=1"
},
},
{
"name": "Launch Frontend",
"type": "node",
"request": "launch",
"runtimeExecutable": "pnpm",
"runtimeArgs": [
"run",
"dev"
],
"cwd": "${workspaceFolder}/frontend",
"serverReadyAction": {
"action": "debugWithChrome",
"pattern": "Local: http://localhost:([0-9]+)",
"uriFormat": "http://localhost:%s",
"webRoot": "${workspaceFolder}/frontend"
}
}
]
}

28
.vscode/settings.json vendored
View file

@ -1,7 +1,4 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"yaml.schemas": {
"https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml"
},
@ -10,5 +7,28 @@
"package.json": "package-lock.json, yarn.lock, .eslintrc.js, tsconfig.json, .prettierrc, .editorconfig, pnpm-lock.yaml, postcss.config.js, tailwind.config.js",
"docker-compose.yml": "Dockerfile, .dockerignore, docker-compose.dev.yml, docker-compose.yml",
"README.md": "LICENSE, SECURITY.md"
}
},
"cSpell.words": [
"debughandlers",
"Homebox"
],
// use ESLint to format code on save
"editor.formatOnSave": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"eslint.format.enable": true,
"css.validate": false,
"tailwindCSS.includeLanguages": {
"vue": "html",
"vue-html": "html"
},
"editor.quickSuggestions": {
"strings": true
},
"tailwindCSS.experimental.configFile": "./frontend/tailwind.config.js"
}

57
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,57 @@
# Contributing
## We Develop with GitHub
We use GitHub to host code, to track issues and feature requests, as well as accept pull requests.
## Branch Flow
We use the `main` branch as the development branch. All PRs should be made to the `main` branch from a feature branch. To create a pull request, you can use the following steps:
1. Fork the repository and create a new branch from `main`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure that the test suite and linters pass
5. Issue your pull request
## How To Get Started
### Prerequisites
There is a devcontainer available for this project. If you are using VSCode, you can use the devcontainer to get started. If you are not using VSCode, you need to ensure that you have the following tools installed:
- [Go 1.19+](https://golang.org/doc/install)
- [Swaggo](https://github.com/swaggo/swag)
- [Node.js 16+](https://nodejs.org/en/download/)
- [pnpm](https://pnpm.io/installation)
- [Taskfile](https://taskfile.dev/#/installation) (Optional but recommended)
- For code generation, you'll need to have `python3` available on your path. In most cases, this is already installed and available.
If you're using `taskfile` you can run `task --list-all` for a list of all commands and their descriptions.
### Setup
If you're using the taskfile, you can use the `task setup` command to run the required setup commands. Otherwise, you can review the commands required in the `Taskfile.yml` file.
Note that when installing dependencies with pnpm you must use the `--shamefully-hoist` flag. If you don't use this flag, you will get an error when running the frontend server.
### API Development Notes
start command `task go:run`
1. API Server does not auto reload. You'll need to restart the server after making changes.
2. Unit tests should be written in Go, however, end-to-end or user story tests should be written in TypeScript using the client library in the frontend directory.
### Frontend Development Notes
start command `task: ui:dev`
1. The frontend is a Vue 3 app with Nuxt.js that uses Tailwind and DaisyUI for styling.
2. We're using Vitest for our automated testing. You can run these with `task ui:watch`.
3. Tests require the API server to be running, and in some cases the first run will fail due to a race condition. If this happens, just run the tests again and they should pass.
## Publishing Release
Create a new tag in GitHub with the version number vX.X.X. This will trigger a new release to be created.
Test -> Goreleaser -> Publish Release -> Trigger Docker Builds -> Deploy Docs + Fly.io Demo

View file

@ -1,45 +1,45 @@
# Build Nuxt
FROM node:17-alpine as frontend-builder
FROM r.batts.cloud/nodejs:18 as frontend-builder
WORKDIR /app
RUN npm install -g pnpm
RUN npm install -g pnpm@latest-9
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --shamefully-hoist
COPY frontend .
RUN pnpm build
# Build API
FROM golang:alpine AS builder
FROM r.batts.cloud/golang:1.24 AS builder
ARG BUILD_TIME
ARG COMMIT
RUN apk update && \
apk upgrade && \
apk add --update git build-base gcc g++
ARG VERSION
RUN apt update && \
apt install -y git build-essential gcc g++
WORKDIR /go/src/app
COPY ./backend .
RUN go get -d -v ./...
RUN rm -rf ./app/api/public
COPY --from=frontend-builder /app/.output/public ./app/api/public
RUN CGO_ENABLED=1 GOOS=linux go build \
-ldflags "-s -w -X main.Commit=$COMMIT -X main.BuildTime=$BUILD_TIME" \
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-o /go/bin/api \
-v ./app/api/*.go
# Production Stage
FROM alpine:latest
FROM r.batts.cloud/debian:trixie
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
COPY --from=builder /go/bin/api /app
RUN chmod +x /app/api
LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
EXPOSE 7745
WORKDIR /app
VOLUME [ "/data" ]

53
Dockerfile.rootless Normal file
View file

@ -0,0 +1,53 @@
# Build Nuxt
FROM node:17-alpine as frontend-builder
WORKDIR /app
RUN npm install -g pnpm
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --shamefully-hoist
COPY frontend .
RUN pnpm build
# Build API
FROM golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
RUN apk update && \
apk upgrade && \
apk add --update git build-base gcc g++
WORKDIR /go/src/app
COPY ./backend .
RUN go get -d -v ./...
RUN rm -rf ./app/api/public
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-o /go/bin/api \
-v ./app/api/*.go && \
chmod +x /go/bin/api && \
# create a directory so that we can copy it in the next stage
mkdir /data
# Production Stage
FROM gcr.io/distroless/static
ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
# Copy the binary and the (empty) /data dir and
# change the ownership to the low-privileged user
COPY --from=builder --chown=nonroot /go/bin/api /app
COPY --from=builder --chown=nonroot /data /data
LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
EXPOSE 7745
VOLUME [ "/data" ]
# Drop root and run as low-privileged user
USER nonroot
ENTRYPOINT [ "/app" ]
CMD [ "/data/config.yml" ]

View file

@ -16,12 +16,28 @@
[Configuration & Docker Compose](https://hay-kot.github.io/homebox/quick-start)
```bash
docker run --name=homebox \
--restart=always \
--publish=3100:7745 \
# If using the rootless image, ensure data
# folder has correct permissions
mkdir -p /path/to/data/folder
chown 65532:65532 -R /path/to/data/folder
docker run -d \
--name homebox \
--restart unless-stopped \
--publish 3100:7745 \
--env TZ=Europe/Bucharest \
--volume /path/to/data/folder/:/data \
ghcr.io/hay-kot/homebox:latest
# 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,63 +1,145 @@
version: "3"
env:
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
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"
tasks:
generate:
desc: |
Generates collateral files from the backend project
including swagger docs and typescripts type for the frontend
deps:
- db:generate
setup:
desc: Install development dependencies
cmds:
- go install github.com/swaggo/swag/cmd/swag@latest
- cd backend && go mod tidy
- cd frontend && pnpm install --shamefully-hoist
swag:
desc: Generate swagger docs
dir: backend/app/api/static/
vars:
API: "../"
INTERNAL: "../../../internal"
PKGS: "../../../pkgs"
cmds:
- swag fmt --dir={{ .API }}
- swag init --dir={{ .API }},{{ .INTERNAL }}/core/services,{{ .INTERNAL }}/data/repo --parseDependency
sources:
- "./backend/app/api/**/*"
- "./backend/internal/data/**"
- "./backend/internal/core/services/**/*"
- "./backend/app/tools/typegen/main.go"
typescript-types:
desc: Generates typescript types from swagger definition
cmds:
- cd backend/app/api/ && swag fmt
- cd backend/app/api/ && swag init --dir=./,../../internal,../../pkgs
- |
npx swagger-typescript-api \
--no-client \
--modular \
--path ./backend/app/api/docs/swagger.json \
--path ./backend/app/api/static/docs/swagger.json \
--output ./frontend/lib/api/types
- python3 ./scripts/process-types.py ./frontend/lib/api/types/data-contracts.ts
- go run ./backend/app/tools/typegen/main.go ./frontend/lib/api/types/data-contracts.ts
sources:
- "./backend/app/api/**/*"
- "./backend/internal/repo/**/*"
- "./backend/internal/services/**/*"
- "./scripts/process-types.py"
generates:
- "./frontend/lib/api/types/data-contracts.ts"
- "./backend/app/api/docs/swagger.json"
- "./backend/app/api/docs/swagger.yaml"
- ./backend/app/tools/typegen/main.go
- ./backend/app/api/static/docs/swagger.json
api:
generate:
deps:
- db:generate
cmds:
- task: swag
- task: typescript-types
- cp ./backend/app/api/static/docs/swagger.json docs/docs/api/openapi-2.0.json
go:run:
desc: Starts the backend api server (depends on generate task)
dir: backend
deps:
- generate
cmds:
- cd backend && go run ./app/api/ {{ .CLI_ARGS }}
- go run ./app/api/ {{ .CLI_ARGS }}
silent: false
api:build:
go:test:
desc: Runs all go tests using gotestsum - supports passing gotestsum args
dir: backend
cmds:
- cd backend && go build ./app/api/
- gotestsum {{ .CLI_ARGS }} ./...
go:coverage:
desc: Runs all go tests with -race flag and generates a coverage report
dir: backend
cmds:
- go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
silent: true
api:test:
go:tidy:
desc: Runs go mod tidy on the backend
dir: backend
cmds:
- cd backend && go test ./app/api/
silent: true
- go mod tidy
api:watch:
go:lint:
desc: Runs golangci-lint
dir: backend
cmds:
- cd backend && gotestsum --watch ./...
- golangci-lint run ./...
api:coverage:
go:all:
desc: Runs all go test and lint related tasks
cmds:
- cd backend && go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
silent: true
- task: go:tidy
- task: go:lint
- task: go:test
go:build:
desc: Builds the backend binary
dir: backend
cmds:
- go build -o ../build/backend ./app/api
db:generate:
desc: Run Entgo.io Code Generation
dir: backend/internal/
cmds:
- |
go generate ./...
sources:
- "./backend/internal/data/ent/schema/**/*"
db:migration:
desc: Runs the database diff engine to generate a SQL migration files
deps:
- db:generate
cmds:
- cd backend && go run app/tools/migrations/main.go {{ .CLI_ARGS }}
ui:watch:
desc: Starts the vitest test runner in watch mode
dir: frontend
cmds:
- pnpm run test:watch
ui:dev:
desc: Run frontend development server
dir: frontend
cmds:
- pnpm dev
ui:fix:
desc: Runs prettier and eslint on the frontend
dir: frontend
cmds:
- pnpm run lint:fix
ui:check:
desc: Runs type checking
dir: frontend
cmds:
- pnpm run typecheck
test:ci:
desc: Runs end-to-end test on a live server (only for use in CI)
cmds:
- cd backend && go build ./app/api
- backend/api &
@ -65,30 +147,11 @@ tasks:
- cd frontend && pnpm run test:ci
silent: true
frontend:watch:
desc: Starts the vitest test runner in watch mode
pr:
desc: Runs all tasks required for a PR
cmds:
- cd frontend && pnpm vitest --watch
frontend:
desc: Run frontend development server
cmds:
- cd frontend && pnpm dev
db:generate:
desc: Run Entgo.io Code Generation
cmds:
- |
cd backend && go generate ./... \
--template=ent/schema/templates/has_id.tmpl
sources:
- "./backend/ent/schema/**/*"
generates:
- "./backend/ent/"
db:migration:
desc: Runs the database diff engine to generate a SQL migration files
deps:
- db:generate
cmds:
- cd backend && go run app/migrations/main.go {{ .CLI_ARGS }}
- task: generate
- task: go:all
- task: ui:check
- task: ui:fix
- task: test:ci

2
backend/.gitignore vendored Normal file
View file

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

74
backend/.golangci.yml Normal file
View file

@ -0,0 +1,74 @@
run:
timeout: 10m
skip-dirs:
- internal/data/ent.*
linters-settings:
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: true

54
backend/.goreleaser.yaml Normal file
View file

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

View file

@ -1,23 +1,21 @@
package main
import (
"time"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/config"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"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/homebox/backend/pkgs/server"
)
type app struct {
conf *config.Config
mailer mailer.Mailer
db *ent.Client
server *server.Server
repos *repo.AllRepos
services *services.AllServices
bus *eventbus.EventBus
}
func new(conf *config.Config) *app {
@ -35,10 +33,3 @@ func new(conf *config.Config) *app {
return s
}
func (a *app) startBgTask(t time.Duration, fn func()) {
for {
a.server.Background(fn)
time.Sleep(t)
}
}

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

@ -2,57 +2,61 @@ package main
import (
"context"
"encoding/csv"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/rs/zerolog/log"
)
func (a *app) SetupDemo() {
csvText := `Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Model Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,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,,,,,,,
`
var (
registration = services.UserRegistration{
Email: "demo@email.com",
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
registration := services.UserRegistration{
Email: "demo@example.com",
Name: "Demo",
Password: "demo",
}
)
// First check if we've already setup a demo user and skip if so
_, err := a.services.User.Login(context.Background(), registration.Email, registration.Password)
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")
}
token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password)
self, _ := a.services.User.GetSelf(context.Background(), token.Raw)
// Read CSV Text
reader := csv.NewReader(strings.NewReader(csvText))
reader.Comma = ','
records, err := reader.ReadAll()
token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err != nil {
log.Err(err).Msg("Failed to read CSV")
log.Err(err).Msg("Failed to login demo user")
log.Fatal().Msg("Failed to setup demo")
return
}
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")
return
}
err = a.services.Items.CsvImport(context.Background(), self.GroupID, records)
_, 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")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
// Package debughandlers provides handlers for debugging.
package debughandlers
import (
"expvar"
"net/http"
"net/http/pprof"
)
func New(mux *http.ServeMux) {
mux.HandleFunc("/debug/pprof", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
mux.Handle("/debug/vars", expvar.Handler())
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1,213 @@
// Package v1 provides the API handlers for version 1 of the API.
package v1
import (
"encoding/json"
"net/http"
"time"
"github.com/google/uuid"
"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/repo"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
"github.com/olahol/melody"
)
type Results[T any] struct {
Items []T `json:"items"`
}
func WrapResults[T any](items []T) Results[T] {
return Results[T]{Items: items}
}
type Wrapped struct {
Item interface{} `json:"item"`
}
func Wrap(v any) Wrapped {
return Wrapped{Item: v}
}
func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.maxUploadSize = maxUploadSize
}
}
func WithDemoStatus(demoStatus bool) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.isDemo = demoStatus
}
}
func WithRegistration(allowRegistration bool) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.allowRegistration = allowRegistration
}
}
func WithSecureCookies(secure bool) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.cookieSecure = secure
}
}
type V1Controller struct {
cookieSecure bool
repo *repo.AllRepos
svc *services.AllServices
maxUploadSize int64
isDemo bool
allowRegistration bool
bus *eventbus.EventBus
}
type (
ReadyFunc func() bool
Build struct {
Version string `json:"version"`
Commit string `json:"commit"`
BuildTime string `json:"buildTime"`
}
APISummary struct {
Healthy bool `json:"health"`
Versions []string `json:"versions"`
Title string `json:"title"`
Message string `json:"message"`
Build Build `json:"build"`
Demo bool `json:"demo"`
AllowRegistration bool `json:"allowRegistration"`
}
)
func BaseURLFunc(prefix string) func(s string) string {
return func(s string) string {
return prefix + "/v1" + s
}
}
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, options ...func(*V1Controller)) *V1Controller {
ctrl := &V1Controller{
repo: repos,
svc: svc,
allowRegistration: true,
bus: bus,
}
for _, opt := range options {
opt(ctrl)
}
return ctrl
}
// HandleBase godoc
//
// @Summary Application Info
// @Tags Base
// @Produce json
// @Success 200 {object} APISummary
// @Router /v1/status [GET]
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
return server.JSON(w, http.StatusOK, APISummary{
Healthy: ready(),
Title: "Homebox",
Message: "Track, Manage, and Organize your Things",
Build: build,
Demo: ctrl.isDemo,
AllowRegistration: ctrl.allowRegistration,
})
}
}
// 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)
})
factory := func(e string) func(data any) {
return func(data any) {
eventData, ok := data.(eventbus.GroupMutationEvent)
if !ok {
log.Log().Msgf("invalid event data: %v", data)
return
}
msg := &eventMsg{Event: e}
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
}
GID := groupIDStr.(uuid.UUID)
return GID == eventData.GID
})
}
}
ctrl.bus.Subscribe(eventbus.EventLabelMutation, factory("label.mutation"))
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

@ -0,0 +1,27 @@
package v1
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
)
// routeID extracts the ID from the request URL. If the ID is not in a valid
// format, an error is returned. If a error is returned, it can be directly returned
// from the handler. the validate.ErrInvalidID error is known by the error middleware
// and will be handled accordingly.
//
// Example: /api/v1/ac614db5-d8b8-4659-9b14-6e913a6eb18a -> uuid.UUID{ac614db5-d8b8-4659-9b14-6e913a6eb18a}
func (ctrl *V1Controller) routeID(r *http.Request) (uuid.UUID, error) {
return ctrl.routeUUID(r, "id")
}
func (ctrl *V1Controller) routeUUID(r *http.Request, key string) (uuid.UUID, error) {
ID, err := uuid.Parse(chi.URLParam(r, key))
if err != nil {
return uuid.Nil, validate.NewRouteKeyError(key)
}
return ID, nil
}

View file

@ -0,0 +1,36 @@
package v1
import (
"net/url"
"strconv"
"github.com/google/uuid"
)
func queryUUIDList(params url.Values, key string) []uuid.UUID {
var ids []uuid.UUID
for _, id := range params[key] {
uid, err := uuid.Parse(id)
if err != nil {
continue
}
ids = append(ids, uid)
}
return ids
}
func queryIntOrNegativeOne(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
return -1
}
return i
}
func queryBool(s string) bool {
b, err := strconv.ParseBool(s)
if err != nil {
return false
}
return b
}

View file

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

View file

@ -0,0 +1,62 @@
package v1
import (
"net/http"
"strconv"
"strings"
"github.com/go-chi/chi/v5"
"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/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
)
// HandleAssetGet godocs
//
// @Summary Get Item by Asset ID
// @Tags Items
// @Produce json
// @Param id path string true "Asset ID"
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/assets/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
assetIDParam := chi.URLParam(r, "id")
assetIDParam = strings.ReplaceAll(assetIDParam, "-", "") // Remove dashes
// Convert the asset ID to an int64
assetID, err := strconv.ParseInt(assetIDParam, 10, 64)
if err != nil {
return err
}
pageParam := r.URL.Query().Get("page")
var page int64 = -1
if pageParam != "" {
page, err = strconv.ParseInt(pageParam, 10, 64)
if err != nil {
return server.JSON(w, http.StatusBadRequest, "Invalid page number")
}
}
pageSizeParam := r.URL.Query().Get("pageSize")
var pageSize int64 = -1
if pageSizeParam != "" {
pageSize, err = strconv.ParseInt(pageSizeParam, 10, 64)
if err != nil {
return server.JSON(w, http.StatusBadRequest, "Invalid page size")
}
}
items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GID, repo.AssetID(assetID), int(page), int(pageSize))
if err != nil {
log.Err(err).Msg("failed to get item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusOK, items)
}
}

View file

@ -0,0 +1,251 @@
package v1
import (
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
)
const (
cookieNameToken = "hb.auth.token"
cookieNameRemember = "hb.auth.remember"
cookieNameSession = "hb.auth.session"
)
type (
TokenResponse struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
AttachmentToken string `json:"attachmentToken"`
}
LoginForm struct {
Username string `json:"username"`
Password string `json:"password"`
StayLoggedIn bool `json:"stayLoggedIn"`
}
)
type CookieContents struct {
Token string
ExpiresAt time.Time
Remember bool
}
func GetCookies(r *http.Request) (*CookieContents, error) {
cookie, err := r.Cookie(cookieNameToken)
if err != nil {
return nil, errors.New("authorization cookie is required")
}
rememberCookie, err := r.Cookie(cookieNameRemember)
if err != nil {
return nil, errors.New("remember cookie is required")
}
return &CookieContents{
Token: cookie.Value,
ExpiresAt: cookie.Expires,
Remember: rememberCookie.Value == "true",
}, 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
// @Tags Authentication
// @Accept x-www-form-urlencoded
// @Accept application/json
// @Param username formData string false "string" example(admin@admin.com)
// @Param password formData string false "string" example(admin)
// @Param payload body LoginForm true "Login Data"
// @Param provider query string false "auth provider"
// @Produce json
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFunc {
if len(ps) == 0 {
panic("no auth providers provided")
}
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 {
// Extract provider query
provider := r.URL.Query().Get("provider")
if provider == "" {
provider = "local"
}
// Get the provider
p, ok := providers[provider]
if !ok {
return validate.NewRequestError(errors.New("invalid auth provider"), http.StatusBadRequest)
}
newToken, err := p.Authenticate(w, r)
if err != nil {
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, true)
return server.JSON(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
AttachmentToken: newToken.AttachmentToken,
})
}
}
// HandleAuthLogout godoc
//
// @Summary User Logout
// @Tags Authentication
// @Success 204
// @Router /v1/users/logout [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context())
if token == "" {
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
}
err := ctrl.svc.User.Logout(r.Context(), token)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
ctrl.unsetCookies(w, noPort(r.Host))
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleAuthRefresh godoc
//
// @Summary User Token Refresh
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
// @Description This does not validate that the user still exists within the database.
// @Tags Authentication
// @Success 200
// @Router /v1/users/refresh [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
requestToken := services.UseTokenCtx(r.Context())
if requestToken == "" {
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
}
newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken)
if err != nil {
return validate.NewUnauthorizedError()
}
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, false)
return server.JSON(w, http.StatusOK, newToken)
}
}
func noPort(host string) string {
return strings.Split(host, ":")[0]
}
func (ctrl *V1Controller) setCookies(w http.ResponseWriter, domain, token string, expires time.Time, remember bool) {
http.SetCookie(w, &http.Cookie{
Name: cookieNameRemember,
Value: strconv.FormatBool(remember),
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set HTTP only cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameToken,
Value: token,
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set Fake Session cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameSession,
Value: "true",
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: false,
Path: "/",
})
}
func (ctrl *V1Controller) unsetCookies(w http.ResponseWriter, domain string) {
http.SetCookie(w, &http.Cookie{
Name: cookieNameToken,
Value: "",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
http.SetCookie(w, &http.Cookie{
Name: cookieNameRemember,
Value: "false",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set Fake Session cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameSession,
Value: "false",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: false,
Path: "/",
})
}

View file

@ -0,0 +1,97 @@
package v1
import (
"net/http"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain"
)
type (
GroupInvitationCreate struct {
Uses int `json:"uses" validate:"required,min=1,max=100"`
ExpiresAt time.Time `json:"expiresAt"`
}
GroupInvitation struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
Uses int `json:"uses"`
}
)
// HandleGroupGet godoc
//
// @Summary Get Group
// @Tags Group
// @Produce json
// @Success 200 {object} repo.Group
// @Router /v1/groups [Get]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc {
fn := func(r *http.Request) (repo.Group, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.GroupByID(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupUpdate godoc
//
// @Summary Update Group
// @Tags Group
// @Produce json
// @Param payload body repo.GroupUpdate true "User Data"
// @Success 200 {object} repo.Group
// @Router /v1/groups [Put]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) {
auth := services.NewContext(r.Context())
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)
}
return adapters.Action(fn, http.StatusOK)
}
// HandleGroupInvitationsCreate godoc
//
// @Summary Create Group Invitation
// @Tags Group
// @Produce json
// @Param payload body GroupInvitationCreate true "User Data"
// @Success 200 {object} GroupInvitation
// @Router /v1/groups/invitations [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupInvitationsCreate() errchain.HandlerFunc {
fn := func(r *http.Request, body GroupInvitationCreate) (GroupInvitation, error) {
if body.ExpiresAt.IsZero() {
body.ExpiresAt = time.Now().Add(time.Hour * 24)
}
auth := services.NewContext(r.Context())
token, err := ctrl.svc.Group.NewInvitation(auth, body.Uses, body.ExpiresAt)
return GroupInvitation{
Token: token,
ExpiresAt: body.ExpiresAt,
Uses: body.Uses,
}, err
}
return adapters.Action(fn, http.StatusCreated)
}

View file

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

View file

@ -0,0 +1,199 @@
package v1
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"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
)
type (
ItemAttachmentToken struct {
Token string `json:"token"`
}
)
// HandleItemAttachmentCreate godocs
//
// @Summary Create Item Attachment
// @Tags Items Attachments
// @Produce json
// @Param id path string true "Item ID"
// @Param file formData file true "File attachment"
// @Param type formData string true "Type of file"
// @Param name formData string true "name of the file including extension"
// @Success 200 {object} repo.ItemOut
// @Failure 422 {object} validate.ErrorResponse
// @Router /v1/items/{id}/attachments [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
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()
file, _, err := r.FormFile("file")
if err != nil {
switch {
case errors.Is(err, http.ErrMissingFile):
log.Debug().Msg("file for attachment is missing")
errs = errs.Append("file", "file is required")
default:
log.Err(err).Msg("failed to get file from form")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
}
attachmentName := r.FormValue("name")
if attachmentName == "" {
log.Debug().Msg("failed to get name from form")
errs = errs.Append("name", "name is required")
}
if !errs.Nil() {
return server.JSON(w, http.StatusUnprocessableEntity, errs)
}
attachmentType := r.FormValue("type")
if attachmentType == "" {
// Attempt to auto-detect the type of the file
ext := filepath.Ext(attachmentName)
switch strings.ToLower(ext) {
case ".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff":
attachmentType = attachment.TypePhoto.String()
default:
attachmentType = attachment.TypeAttachment.String()
}
}
id, err := ctrl.routeID(r)
if err != nil {
return err
}
ctx := services.NewContext(r.Context())
item, err := ctrl.svc.Items.AttachmentAdd(
ctx,
id,
attachmentName,
attachment.Type(attachmentType),
file,
)
if err != nil {
log.Err(err).Msg("failed to add attachment")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusCreated, item)
}
}
// HandleItemAttachmentGet godocs
//
// @Summary Get Item Attachment
// @Tags Items Attachments
// @Produce application/octet-stream
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 200 {object} ItemAttachmentToken
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentGet() errchain.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentDelete godocs
//
// @Summary Delete Item Attachment
// @Tags Items Attachments
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 204
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDelete() errchain.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentUpdate godocs
//
// @Summary Update Item Attachment
// @Tags Items Attachments
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() errchain.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) error {
ID, err := ctrl.routeID(r)
if err != nil {
return err
}
attachmentID, err := ctrl.routeUUID(r, "attachment_id")
if err != nil {
return err
}
ctx := services.NewContext(r.Context())
switch r.Method {
case http.MethodGet:
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), attachmentID)
if err != nil {
log.Err(err).Msg("failed to get attachment path")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
http.ServeFile(w, r, doc.Path)
return nil
// Delete Attachment Handler
case http.MethodDelete:
err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentID)
if err != nil {
log.Err(err).Msg("failed to delete attachment")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusNoContent, nil)
// Update Attachment Handler
case http.MethodPut:
var attachment repo.ItemAttachmentUpdate
err = server.Decode(r, &attachment)
if err != nil {
log.Err(err).Msg("failed to decode attachment")
return validate.NewRequestError(err, http.StatusBadRequest)
}
attachment.ID = attachmentID
val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ID, &attachment)
if err != nil {
log.Err(err).Msg("failed to delete attachment")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusOK, val)
}
return nil
}

View file

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

View file

@ -0,0 +1,122 @@
package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain"
)
// HandleLocationTreeQuery godoc
//
// @Summary Get Locations Tree
// @Tags Locations
// @Produce json
// @Param withItems query bool false "include items in response tree"
// @Success 200 {object} []repo.TreeItem
// @Router /v1/locations/tree [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc {
fn := func(r *http.Request, query repo.TreeQuery) ([]repo.TreeItem, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.Tree(auth, auth.GID, query)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleLocationGetAll godoc
//
// @Summary Get All Locations
// @Tags Locations
// @Produce json
// @Param filterChildren query bool false "Filter locations with parents"
// @Success 200 {object} []repo.LocationOutCount
// @Router /v1/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc {
fn := func(r *http.Request, q repo.LocationQuery) ([]repo.LocationOutCount, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.GetAll(auth, auth.GID, q)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleLocationCreate godoc
//
// @Summary Create Location
// @Tags Locations
// @Produce json
// @Param payload body repo.LocationCreate true "Location Data"
// @Success 200 {object} repo.LocationSummary
// @Router /v1/locations [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc {
fn := func(r *http.Request, createData repo.LocationCreate) (repo.LocationOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.Create(auth, auth.GID, createData)
}
return adapters.Action(fn, http.StatusCreated)
}
// HandleLocationDelete godoc
//
// @Summary Delete Location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 204
// @Router /v1/locations/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}
// HandleLocationGet godoc
//
// @Summary Get Location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleLocationUpdate godoc
//
// @Summary Update Location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Param payload body repo.LocationUpdate true "Location Data"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) {
auth := services.NewContext(r.Context())
body.ID = ID
return ctrl.repo.Locations.UpdateByGroup(auth, auth.GID, ID, body)
}
return adapters.ActionID("id", fn, http.StatusOK)
}

View file

@ -0,0 +1,82 @@
package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain"
)
// HandleMaintenanceLogGet godoc
//
// @Summary Get Maintenance Log
// @Tags Maintenance
// @Produce json
// @Success 200 {object} repo.MaintenanceLog
// @Router /v1/items/{id}/maintenance [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q)
}
return adapters.QueryID("id", fn, http.StatusOK)
}
// HandleMaintenanceEntryCreate godoc
//
// @Summary Create Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
// @Success 201 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc {
fn := func(r *http.Request, itemID uuid.UUID, body repo.MaintenanceEntryCreate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.Create(auth, itemID, body)
}
return adapters.ActionID("id", fn, http.StatusCreated)
}
// HandleMaintenanceEntryDelete godoc
//
// @Summary Delete Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Success 204
// @Router /v1/items/{id}/maintenance/{entry_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.MaintEntry.Delete(auth, entryID)
return nil, err
}
return adapters.CommandID("entry_id", fn, http.StatusNoContent)
}
// HandleMaintenanceEntryUpdate godoc
//
// @Summary Update Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance/{entry_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.Update(auth, entryID, body)
}
return adapters.ActionID("entry_id", fn, http.StatusOK)
}

View file

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

View file

@ -0,0 +1,72 @@
package v1
import (
"bytes"
"image/png"
"io"
"net/http"
"net/url"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
_ "embed"
)
//go:embed assets/QRIcon.png
var qrcodeLogo []byte
// HandleGenerateQRCode godoc
//
// @Summary Create QR Code
// @Tags Items
// @Produce json
// @Param data query string false "data to be encoded into qrcode"
// @Success 200 {string} string "image/jpeg"
// @Router /v1/qrcode [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGenerateQRCode() errchain.HandlerFunc {
type query struct {
// 4,296 characters is the maximum length of a QR code
Data string `schema:"data" validate:"required,max=4296"`
}
return func(w http.ResponseWriter, r *http.Request) error {
q, err := adapters.DecodeQuery[query](r)
if err != nil {
return err
}
image, err := png.Decode(bytes.NewReader(qrcodeLogo))
if err != nil {
panic(err)
}
decodedStr, err := url.QueryUnescape(q.Data)
if err != nil {
return err
}
qrc, err := qrcode.New(decodedStr)
if err != nil {
return err
}
toWriteCloser := struct {
io.Writer
io.Closer
}{
Writer: w,
Closer: io.NopCloser(nil),
}
qrwriter := standard.NewWithWriter(toWriteCloser, standard.WithLogoImage(image))
// Return the QR code as a jpeg image
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Content-Disposition", "attachment; filename=qrcode.jpg")
return qrc.Save(qrwriter)
}
}

View file

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

View file

@ -0,0 +1,104 @@
package v1
import (
"net/http"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
)
// HandleGroupStatisticsLocations godoc
//
// @Summary Get Location Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupStatisticsLabels godoc
//
// @Summary Get Label Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc {
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupStatistics godoc
//
// @Summary Get Group Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} repo.GroupStatistics
// @Router /v1/groups/statistics [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc {
fn := func(r *http.Request) (repo.GroupStatistics, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsGroup(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
}
// HandleGroupStatisticsPriceOverTime godoc
//
// @Summary Get Purchase Price Statistics
// @Tags Statistics
// @Produce json
// @Success 200 {object} repo.ValueOverTime
// @Param start query string false "start date"
// @Param end query string false "end date"
// @Router /v1/groups/statistics/purchase-price [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerFunc {
parseDate := func(datestr string, defaultDate time.Time) (time.Time, error) {
if datestr == "" {
return defaultDate, nil
}
return time.Parse("2006-01-02", datestr)
}
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
startDate, err := parseDate(r.URL.Query().Get("start"), time.Now().AddDate(0, -1, 0))
if err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
endDate, err := parseDate(r.URL.Query().Get("end"), time.Now())
if err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
stats, err := ctrl.repo.Groups.StatsPurchasePrice(ctx, ctx.GID, startDate, endDate)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusOK, stats)
}
}

View file

@ -0,0 +1,154 @@
package v1
import (
"fmt"
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
)
// HandleUserRegistration godoc
//
// @Summary Register New User
// @Tags User
// @Produce json
// @Param payload body services.UserRegistration true "User Data"
// @Success 204
// @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
regData := services.UserRegistration{}
if err := server.Decode(r, &regData); err != nil {
log.Err(err).Msg("failed to decode user registration data")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
if !ctrl.allowRegistration && regData.GroupToken == "" {
return validate.NewRequestError(fmt.Errorf("user registration disabled"), http.StatusForbidden)
}
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
if err != nil {
log.Err(err).Msg("failed to register user")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleUserSelf godoc
//
// @Summary Get User Self
// @Tags User
// @Produce json
// @Success 200 {object} Wrapped{item=repo.UserOut}
// @Router /v1/users/self [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelf() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context())
usr, err := ctrl.svc.User.GetSelf(r.Context(), token)
if usr.ID == uuid.Nil || err != nil {
log.Err(err).Msg("failed to get user")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusOK, Wrap(usr))
}
}
// HandleUserSelfUpdate godoc
//
// @Summary Update Account
// @Tags User
// @Produce json
// @Param payload body repo.UserUpdate true "User Data"
// @Success 200 {object} Wrapped{item=repo.UserUpdate}
// @Router /v1/users/self [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfUpdate() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
updateData := repo.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil {
log.Err(err).Msg("failed to decode user update data")
return validate.NewRequestError(err, http.StatusBadRequest)
}
actor := services.UseUserCtx(r.Context())
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusOK, Wrap(newData))
}
}
// HandleUserSelfDelete godoc
//
// @Summary Delete Account
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden)
}
actor := services.UseUserCtx(r.Context())
if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusNoContent, nil)
}
}
type (
ChangePassword struct {
Current string `json:"current,omitempty"`
New string `json:"new,omitempty"`
}
)
// HandleUserSelfChangePassword godoc
//
// @Summary Change Password
// @Tags User
// @Success 204
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden)
}
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.ChangePassword(ctx, cp.Current, cp.New)
if !ok {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.JSON(w, http.StatusNoContent, nil)
}
}

View file

@ -2,9 +2,8 @@ package main
import (
"os"
"strings"
"github.com/hay-kot/homebox/backend/internal/config"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
@ -15,27 +14,11 @@ func (a *app) setupLogger() {
// Logger Init
// zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
if a.conf.Log.Format != config.LogFormatJSON {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
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,48 +1,68 @@
package main
import (
"bytes"
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql/schema"
"github.com/hay-kot/homebox/backend/app/api/docs"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/config"
"github.com/hay-kot/homebox/backend/internal/migrations"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
_ "github.com/mattn/go-sqlite3"
"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"
"github.com/hay-kot/homebox/backend/internal/data/migrations"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/graceful"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
_ "github.com/hay-kot/homebox/backend/pkgs/cgofreesqlite"
)
var (
Version = "0.1.0"
Commit = "HEAD"
BuildTime = "now"
version = "nightly"
commit = "HEAD"
buildTime = "now"
)
// @title Go API Templates
func build() string {
short := commit
if len(short) > 7 {
short = short[:7]
}
return fmt.Sprintf("%s, commit %s, built at %s", version, short, buildTime)
}
// @title Homebox API
// @version 1.0
// @description This is a simple Rest API Server Template that implements some basic User and Authentication patterns to help you get started and bootstrap your next project!.
// @description Track, Manage, and Organize your Things.
// @contact.name Don't
// @license.name MIT
// @BasePath /api
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
func main() {
cfg, err := config.New()
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
cfg, err := config.New(build(), "Homebox inventory management system")
if err != nil {
panic(err)
}
docs.SwaggerInfo.Host = cfg.Swagger.Host
if err := run(cfg); err != nil {
panic(err)
}
@ -55,17 +75,17 @@ func run(cfg *config.Config) error {
// =========================================================================
// Initialize Database & Repos
err := os.MkdirAll(cfg.Storage.Data, 0755)
err := os.MkdirAll(cfg.Storage.Data, 0o755)
if err != nil {
log.Fatal().Err(err).Msg("failed to create data directory")
}
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) {
@ -98,7 +118,7 @@ func run(cfg *config.Config) error {
log.Fatal().
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteUrl).
Str("url", cfg.Storage.SqliteURL).
Msg("failed creating schema resources")
}
@ -108,42 +128,145 @@ 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, cfg.Storage.Data)
app.services = services.NewServices(app.repos)
app.repos = repo.New(c, app.bus, cfg.Storage.Data)
app.services = services.New(
app.repos,
services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID),
services.WithCurrencies(currencies),
)
// =========================================================================
// Start Server
app.server = server.NewServer(
server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port),
logger := log.With().Caller().Logger()
router := chi.NewMux()
router.Use(
middleware.RequestID,
middleware.RealIP,
mid.Logger(logger),
middleware.Recoverer,
middleware.StripSlashes,
)
routes := app.newRouter(app.repos)
chain := errchain.New(mid.Errors(logger))
if app.conf.Mode != config.ModeDevelopment {
app.logRoutes(routes)
app.mountRoutes(router, chain, app.repos)
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,
}
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
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.startBgTask(time.Duration(24)*time.Hour, func() {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background())
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired tokens")
}
})
runner.AddFunc("eventbus", app.bus.Run)
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
})
return app.server.Start(routes)
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")
}
}))
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")
}
}))
runner.AddPlugin(NewTask("send-notifications", time.Duration(1)*time.Hour, func(ctx context.Context) {
now := time.Now()
if now.Hour() == 8 {
fmt.Println("run notifiers")
err := app.services.BackgroundService.SendNotifiersToday(context.Background())
if err != nil {
log.Error().
Err(err).
Msg("failed to send notifiers")
}
}
}))
if cfg.Debug.Enabled {
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 runner.Start(context.Background())
}

View file

@ -1,160 +1,154 @@
package main
import (
"fmt"
"context"
"errors"
"net/http"
"net/url"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/hay-kot/homebox/backend/internal/config"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
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"
)
func (a *app) setGlobalMiddleware(r *chi.Mux) {
// =========================================================================
// Middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(mwStripTrailingSlash)
// Use struct logger in production for requests, but use
// pretty console logger in development.
if a.conf.Mode == config.ModeDevelopment {
r.Use(a.mwSummaryLogger)
} else {
r.Use(a.mwStructLogger)
type tokenHasKey struct {
key string
}
r.Use(middleware.Recoverer)
// Set a timeout value on the request context (ctx), that will signal
// through ctx.Done() that the request has timed out and further
// processing should be stopped.
r.Use(middleware.Timeout(60 * time.Second))
var hashedToken = tokenHasKey{key: "hashedToken"}
type RoleMode int
const (
RoleModeOr RoleMode = 0
RoleModeAnd RoleMode = 1
)
// mwRoles is a middleware that will validate the required roles are met. All roles
// are required to be met for the request to be allowed. If the user does not have
// the required roles, a 403 Forbidden will be returned.
//
// WARNING: This middleware _MUST_ be called after mwAuthToken or else it will panic
func (a *app) mwRoles(rm RoleMode, required ...string) errchain.Middleware {
return func(next errchain.Handler) errchain.Handler {
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
maybeToken := ctx.Value(hashedToken)
if maybeToken == nil {
panic("mwRoles: token not found in context, you must call mwAuthToken before mwRoles")
}
token := maybeToken.(string)
roles, err := a.repos.AuthTokens.GetRoles(r.Context(), token)
if err != nil {
return err
}
outer:
switch rm {
case RoleModeOr:
for _, role := range required {
if roles.Contains(role) {
break outer
}
}
return validate.NewRequestError(errors.New("Forbidden"), http.StatusForbidden)
case RoleModeAnd:
for _, req := range required {
if !roles.Contains(req) {
return validate.NewRequestError(errors.New("Unauthorized"), http.StatusForbidden)
}
}
}
return next.ServeHTTP(w, r)
})
}
}
type KeyFunc func(r *http.Request) (string, error)
func getBearer(r *http.Request) (string, error) {
auth := r.Header.Get("Authorization")
if auth == "" {
return "", errors.New("authorization header is required")
}
return auth, nil
}
func getQuery(r *http.Request) (string, error) {
token := r.URL.Query().Get("access_token")
if token == "" {
return "", errors.New("access_token query is required")
}
token, err := url.QueryUnescape(token)
if err != nil {
return "", errors.New("access_token query is required")
}
return token, nil
}
// mwAuthToken is a middleware that will check the database for a stateful token
// and attach it to the request context with the user, or return a 401 if it doesn't exist.
func (a *app) mwAuthToken(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestToken := r.Header.Get("Authorization")
// and attach it's user to the request context, or return an appropriate error.
// Authorization support is by token via Headers or Query Parameter
//
// Example:
// - header = "Bearer 1234567890"
// - query = "?access_token=1234567890"
func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler {
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
var requestToken string
// We ignore the error to allow the next strategy to be attempted
{
cookies, _ := v1.GetCookies(r)
if cookies != nil {
requestToken = cookies.Token
}
}
if requestToken == "" {
server.RespondUnauthorized(w)
return
keyFuncs := [...]KeyFunc{
getBearer,
getQuery,
}
for _, keyFunc := range keyFuncs {
token, err := keyFunc(r)
if err == nil {
requestToken = token
break
}
}
}
if requestToken == "" {
return validate.NewRequestError(errors.New("authorization header or query is required"), http.StatusUnauthorized)
}
requestToken = strings.TrimPrefix(requestToken, "Bearer ")
r = r.WithContext(context.WithValue(r.Context(), hashedToken, requestToken))
usr, err := a.services.User.GetSelf(r.Context(), requestToken)
// Check the database for the token
if err != nil {
server.RespondUnauthorized(w)
return
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))
next.ServeHTTP(w, r)
})
}
// mwAdminOnly is a middleware that extends the mwAuthToken middleware to only allow
// requests from superusers.
// func (a *app) mwAdminOnly(next http.Handler) http.Handler {
// mw := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// usr := services.UseUserCtx(r.Context())
// if !usr.IsSuperuser {
// server.RespondUnauthorized(w)
// return
// }
// next.ServeHTTP(w, r)
// })
// return a.mwAuthToken(mw)
// }
// mqStripTrailingSlash is a middleware that will strip trailing slashes from the request path.
func mwStripTrailingSlash(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
next.ServeHTTP(w, r)
})
}
type StatusRecorder struct {
http.ResponseWriter
Status int
}
func (r *StatusRecorder) WriteHeader(status int) {
r.Status = status
r.ResponseWriter.WriteHeader(status)
}
func (a *app) mwStructLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
record := &StatusRecorder{ResponseWriter: w, Status: http.StatusOK}
next.ServeHTTP(record, r)
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
url := fmt.Sprintf("%s://%s%s %s", scheme, r.Host, r.RequestURI, r.Proto)
log.Info().
Str("id", middleware.GetReqID(r.Context())).
Str("url", url).
Str("method", r.Method).
Str("remote_addr", r.RemoteAddr).
Int("status", record.Status).
Msg(url)
})
}
func (a *app) mwSummaryLogger(next http.Handler) http.Handler {
bold := func(s string) string { return "\033[1m" + s + "\033[0m" }
orange := func(s string) string { return "\033[33m" + s + "\033[0m" }
aqua := func(s string) string { return "\033[36m" + s + "\033[0m" }
red := func(s string) string { return "\033[31m" + s + "\033[0m" }
green := func(s string) string { return "\033[32m" + s + "\033[0m" }
fmtCode := func(code int) string {
switch {
case code >= 500:
return red(fmt.Sprintf("%d", code))
case code >= 400:
return orange(fmt.Sprintf("%d", code))
case code >= 300:
return aqua(fmt.Sprintf("%d", code))
default:
return green(fmt.Sprintf("%d", code))
}
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
record := &StatusRecorder{ResponseWriter: w, Status: http.StatusOK}
next.ServeHTTP(record, r) // Blocks until the next handler returns.
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
url := fmt.Sprintf("%s://%s%s %s", scheme, r.Host, r.RequestURI, r.Proto)
log.Info().
Msgf("%s %s %s",
bold(orange(""+r.Method+"")),
aqua(url),
bold(fmtCode(record.Status)),
)
return next.ServeHTTP(w, r)
})
}

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

@ -3,7 +3,6 @@ package main
import (
"embed"
"errors"
"fmt"
"io"
"mime"
"net/http"
@ -11,11 +10,14 @@ import (
"path/filepath"
"github.com/go-chi/chi/v5"
_ "github.com/hay-kot/homebox/backend/app/api/docs"
v1 "github.com/hay-kot/homebox/backend/app/api/v1"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/rs/zerolog/log"
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
"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/v2" // http-swagger middleware
)
const prefix = "/api"
@ -23,102 +25,145 @@ const prefix = "/api"
var (
ErrDir = errors.New("path is dir")
//go:embed all:public/*
//go:embed all:static/public/*
public embed.FS
)
func (a *app) debugRouter() *http.ServeMux {
dbg := http.NewServeMux()
debughandlers.New(dbg)
return dbg
}
// registerRoutes registers all the routes for the API
func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllRepos) {
registerMimes()
r := chi.NewRouter()
a.setGlobalMiddleware(r)
r.Get("/swagger/*", httpSwagger.Handler(
httpSwagger.URL(fmt.Sprintf("%s://%s/swagger/doc.json", a.conf.Swagger.Scheme, a.conf.Swagger.Host)),
httpSwagger.URL("/swagger/doc.json"),
))
// =========================================================================
// API Version 1
v1Base := v1.BaseUrlFunc(prefix)
v1Ctrl := v1.NewControllerV1(a.services,
v1Base := v1.BaseURLFunc(prefix)
v1Ctrl := v1.NewControllerV1(
a.services,
a.repos,
a.bus,
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
v1.WithRegistration(a.conf.Options.AllowRegistration),
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
)
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: Version,
Commit: Commit,
BuildTime: BuildTime,
}))
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
r.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
r.Get(v1Base("/status"), chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: version,
Commit: commit,
BuildTime: buildTime,
})))
// Attachment download URl needs a `token` query param to be passed in the request.
// and also needs to be outside of the `auth` middleware.
r.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload())
r.Get(v1Base("/currencies"), chain.ToHandlerFunc(v1Ctrl.HandleCurrency()))
r.Group(func(r chi.Router) {
r.Use(a.mwAuthToken)
r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf())
r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate())
r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete())
r.Put(v1Base("/users/self/password"), v1Ctrl.HandleUserUpdatePassword())
r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout())
r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh())
r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword())
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
r.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll())
r.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate())
r.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet())
r.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate())
r.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete())
r.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll())
r.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate())
r.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet())
r.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate())
r.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete())
r.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll())
r.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport())
r.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate())
r.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet())
r.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate())
r.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete())
r.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate())
r.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken())
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate())
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete())
})
r.NotFound(notFoundHandler())
return r
providers := []v1.AuthProvider{
providers.NewLocalProvider(a.services.User),
}
// logRoutes logs the routes of the server that are registered within Server.registerRoutes(). This is useful for debugging.
// See https://github.com/go-chi/chi/issues/332 for details and inspiration.
func (a *app) logRoutes(r *chi.Mux) {
desiredSpaces := 10
r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
walkFunc := func(method string, route string, handler http.Handler, middleware ...func(http.Handler) http.Handler) error {
text := "[" + method + "]"
for len(text) < desiredSpaces {
text = text + " "
userMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
}
fmt.Printf("Registered Route: %s%s\n", text, route)
return nil
r.Get(v1Base("/ws/events"), chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...))
r.Get(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
r.Put(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
r.Delete(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
r.Get(v1Base("/groups/statistics/purchase-price"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
r.Get(v1Base("/groups/statistics/locations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
r.Get(v1Base("/groups/statistics/labels"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
// TODO: I don't like /groups being the URL for users
r.Get(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
r.Put(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
r.Post(v1Base("/actions/set-primary-photos"), chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
r.Get(v1Base("/locations/tree"), chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
r.Get(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...))
r.Put(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
r.Delete(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
r.Get(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
r.Post(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
r.Get(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
r.Put(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
r.Delete(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
r.Get(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
r.Post(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
r.Post(v1Base("/items/import"), chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
r.Get(v1Base("/items/export"), chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...))
r.Get(v1Base("/items/fields"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...))
r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
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...))
r.Post(v1Base("/items/{id}/attachments"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
r.Get(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
r.Post(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
r.Put(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
r.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
r.Get(v1Base("/assets/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
// Notifiers
r.Get(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
r.Post(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))
r.Put(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...))
r.Delete(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...))
r.Post(v1Base("/notifiers/test"), chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...))
// Asset-Like endpoints
assetMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
}
if err := chi.Walk(r, walkFunc); err != nil {
fmt.Printf("Logging err: %s\n", err.Error())
}
r.Get(
v1Base("/qrcode"),
chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...),
)
r.Get(
v1Base("/items/{id}/attachments/{attachment_id}"),
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
)
// Reporting Services
r.Get(v1Base("/reporting/bill-of-materials"), chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
r.NotFound(chain.ToHandlerFunc(notFoundHandler()))
}
func registerMimes() {
@ -135,13 +180,13 @@ func registerMimes() {
// notFoundHandler perform the main logic around handling the internal SPA embed and ensuring that
// the client side routing is handled correctly.
func notFoundHandler() http.HandlerFunc {
func notFoundHandler() errchain.HandlerFunc {
tryRead := func(fs embed.FS, prefix, requestedPath string, w http.ResponseWriter) error {
f, err := fs.Open(path.Join(prefix, requestedPath))
if err != nil {
return err
}
defer f.Close()
defer func() { _ = f.Close() }()
stat, _ := f.Stat()
if stat.IsDir() {
@ -154,17 +199,16 @@ func notFoundHandler() http.HandlerFunc {
return err
}
return func(w http.ResponseWriter, r *http.Request) {
err := tryRead(public, "public", r.URL.Path, w)
if err == nil {
return
}
log.Debug().
Str("path", r.URL.Path).
Msg("served from embed not found - serving index.html")
err = tryRead(public, "public", "index.html", w)
return func(w http.ResponseWriter, r *http.Request) error {
err := tryRead(public, "static/public", r.URL.Path, w)
if err != nil {
panic(err)
// Fallback to the index.html file.
// should succeed in all cases.
err = tryRead(public, "static/public", "index.html", w)
if err != nil {
return err
}
}
return nil
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,84 +0,0 @@
package v1
import (
"net/http"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
)
func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.maxUploadSize = maxUploadSize
}
}
func WithDemoStatus(demoStatus bool) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.isDemo = demoStatus
}
}
type V1Controller struct {
svc *services.AllServices
maxUploadSize int64
isDemo bool
}
type (
Build struct {
Version string `json:"version"`
Commit string `json:"commit"`
BuildTime string `json:"buildTime"`
}
ApiSummary struct {
Healthy bool `json:"health"`
Versions []string `json:"versions"`
Title string `json:"title"`
Message string `json:"message"`
Build Build `json:"build"`
Demo bool `json:"demo"`
}
)
func BaseUrlFunc(prefix string) func(s string) string {
v1Base := prefix + "/v1"
prefixFunc := func(s string) string {
return v1Base + s
}
return prefixFunc
}
func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller {
ctrl := &V1Controller{
svc: svc,
}
for _, opt := range options {
opt(ctrl)
}
return ctrl
}
type ReadyFunc func() bool
// HandleBase godoc
// @Summary Retrieves the basic information about the API
// @Tags Base
// @Produce json
// @Success 200 {object} ApiSummary
// @Router /v1/status [GET]
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
server.Respond(w, http.StatusOK, ApiSummary{
Healthy: ready(),
Title: "Go API Template",
Message: "Welcome to the Go API Template Application!",
Build: build,
Demo: ctrl.isDemo,
})
}
}

View file

@ -1,34 +0,0 @@
package v1
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
/*
This is where we put partial snippets/functions for actions that are commonly
used within the controller class. This _hopefully_ helps with code duplication
and makes it a little more consistent when error handling and logging.
*/
// partialParseIdAndUser parses the ID from the requests URL and pulls the user
// from the context. If either of these fail, it will return an error. When an error
// occurs it will also write the error to the response. As such, if an error is returned
// from this function you can return immediately without writing to the response.
func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *repo.UserOut, error) {
uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
log.Err(err).Msg("failed to parse id")
server.RespondError(w, http.StatusBadRequest, err)
return uuid.Nil, &repo.UserOut{}, err
}
user := services.UseUserCtx(r.Context())
return uid, user, nil
}

View file

@ -1,134 +0,0 @@
package v1
import (
"errors"
"net/http"
"time"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
type (
TokenResponse struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
}
LoginForm struct {
Username string `json:"username"`
Password string `json:"password"`
}
)
// HandleAuthLogin godoc
// @Summary User Login
// @Tags Authentication
// @Accept x-www-form-urlencoded
// @Accept application/json
// @Param username formData string false "string" example(admin@admin.com)
// @Param password formData string false "string" example(admin)
// @Produce json
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
loginForm := &LoginForm{}
switch r.Header.Get("Content-Type") {
case server.ContentFormUrlEncoded:
err := r.ParseForm()
if err != nil {
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
log.Error().Err(err).Msg("failed to parse form")
return
}
loginForm.Username = r.PostFormValue("username")
loginForm.Password = r.PostFormValue("password")
case server.ContentJSON:
err := server.Decode(r, loginForm)
if err != nil {
log.Err(err).Msg("failed to decode login form")
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
return
}
default:
server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
return
}
if loginForm.Username == "" || loginForm.Password == "" {
server.RespondError(w, http.StatusBadRequest, errors.New("username and password are required"))
return
}
newToken, err := ctrl.svc.User.Login(r.Context(), loginForm.Username, loginForm.Password)
if err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
})
}
}
// HandleAuthLogout godoc
// @Summary User Logout
// @Tags Authentication
// @Success 204
// @Router /v1/users/logout [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := services.UseTokenCtx(r.Context())
if token == "" {
server.RespondError(w, http.StatusUnauthorized, errors.New("no token within request context"))
return
}
err := ctrl.svc.User.Logout(r.Context(), token)
if err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
// HandleAuthLogout godoc
// @Summary User Token Refresh
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
// @Description This does not validate that the user still exists within the database.
// @Tags Authentication
// @Success 200
// @Router /v1/users/refresh [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
requestToken := services.UseTokenCtx(r.Context())
if requestToken == "" {
server.RespondError(w, http.StatusUnauthorized, errors.New("no user token found"))
return
}
newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken)
if err != nil {
server.RespondUnauthorized(w)
return
}
server.Respond(w, http.StatusOK, newToken)
}
}

View file

@ -1,62 +0,0 @@
package v1
import (
"net/http"
"time"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
type (
GroupInvitationCreate struct {
Uses int `json:"uses"`
ExpiresAt time.Time `json:"expiresAt"`
}
GroupInvitation struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
Uses int `json:"uses"`
}
)
// HandleUserSelf godoc
// @Summary Get the current user
// @Tags User
// @Produce json
// @Param payload body GroupInvitationCreate true "User Data"
// @Success 200 {object} GroupInvitation
// @Router /v1/groups/invitations [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data := GroupInvitationCreate{}
if err := server.Decode(r, &data); err != nil {
log.Err(err).Msg("failed to decode user registration data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
if data.ExpiresAt.IsZero() {
data.ExpiresAt = time.Now().Add(time.Hour * 24)
}
ctx := services.NewContext(r.Context())
token, err := ctrl.svc.User.NewInvitation(ctx, data.Uses, data.ExpiresAt)
if err != nil {
log.Err(err).Msg("failed to create new token")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusCreated, GroupInvitation{
Token: token,
ExpiresAt: data.ExpiresAt,
Uses: data.Uses,
})
}
}

View file

@ -1,189 +0,0 @@
package v1
import (
"encoding/csv"
"net/http"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleItemsGetAll godoc
// @Summary Get All Items
// @Tags Items
// @Produce json
// @Success 200 {object} server.Results{items=[]repo.ItemSummary}
// @Router /v1/items [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := services.UseUserCtx(r.Context())
items, err := ctrl.svc.Items.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("failed to get items")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, server.Results{Items: items})
}
}
// HandleItemsCreate godoc
// @Summary Create a new item
// @Tags Items
// @Produce json
// @Param payload body repo.ItemCreate true "Item Data"
// @Success 200 {object} repo.ItemSummary
// @Router /v1/items [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := repo.ItemCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
user := services.UseUserCtx(r.Context())
item, err := ctrl.svc.Items.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("failed to create item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusCreated, item)
}
}
// HandleItemDelete godocs
// @Summary deletes a item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 204
// @Router /v1/items/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
err = ctrl.svc.Items.Delete(r.Context(), user.GroupID, uid)
if err != nil {
log.Err(err).Msg("failed to delete item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
// HandleItemGet godocs
// @Summary Gets a item and fields
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
items, err := ctrl.svc.Items.GetOne(r.Context(), user.GroupID, uid)
if err != nil {
log.Err(err).Msg("failed to get item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, items)
}
}
// HandleItemUpdate godocs
// @Summary updates a item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Param payload body repo.ItemUpdate true "Item Data"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := repo.ItemUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
body.ID = uid
result, err := ctrl.svc.Items.Update(r.Context(), user.GroupID, body)
if err != nil {
log.Err(err).Msg("failed to update item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
}
}
// HandleItemsImport godocs
// @Summary imports items into the database
// @Tags Items
// @Produce json
// @Success 204
// @Param csv formData file true "Image to upload"
// @Router /v1/items/import [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil {
log.Err(err).Msg("failed to parse multipart form")
server.RespondServerError(w)
return
}
file, _, err := r.FormFile("csv")
if err != nil {
log.Err(err).Msg("failed to get file from form")
server.RespondServerError(w)
return
}
reader := csv.NewReader(file)
data, err := reader.ReadAll()
if err != nil {
log.Err(err).Msg("failed to read csv")
server.RespondServerError(w)
return
}
user := services.UseUserCtx(r.Context())
err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, data)
if err != nil {
log.Err(err).Msg("failed to import items")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}

View file

@ -1,242 +0,0 @@
package v1
import (
"errors"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
type (
ItemAttachmentToken struct {
Token string `json:"token"`
}
)
// HandleItemsImport godocs
// @Summary imports items into the database
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Param file formData file true "File attachment"
// @Param type formData string true "Type of file"
// @Param name formData string true "name of the file including extension"
// @Success 200 {object} repo.ItemOut
// @Failure 422 {object} []server.ValidationError
// @Router /v1/items/{id}/attachments [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil {
log.Err(err).Msg("failed to parse multipart form")
server.RespondError(w, http.StatusBadRequest, errors.New("failed to parse multipart form"))
return
}
errs := make(server.ValidationErrors, 0)
file, _, err := r.FormFile("file")
if err != nil {
switch {
case errors.Is(err, http.ErrMissingFile):
log.Debug().Msg("file for attachment is missing")
errs = errs.Append("file", "file is required")
default:
log.Err(err).Msg("failed to get file from form")
server.RespondServerError(w)
return
}
}
attachmentName := r.FormValue("name")
if attachmentName == "" {
log.Debug().Msg("failed to get name from form")
errs = errs.Append("name", "name is required")
}
if errs.HasErrors() {
server.Respond(w, http.StatusUnprocessableEntity, errs)
return
}
attachmentType := r.FormValue("type")
if attachmentType == "" {
attachmentType = attachment.TypeAttachment.String()
}
id, _, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
ctx := services.NewContext(r.Context())
item, err := ctrl.svc.Items.AttachmentAdd(
ctx,
id,
attachmentName,
attachment.Type(attachmentType),
file,
)
if err != nil {
log.Err(err).Msg("failed to add attachment")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusCreated, item)
}
}
// HandleItemAttachmentGet godocs
// @Summary retrieves an attachment for an item
// @Tags Items
// @Produce application/octet-stream
// @Param id path string true "Item ID"
// @Param token query string true "Attachment token"
// @Success 200
// @Router /v1/items/{id}/attachments/download [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := server.GetParam(r, "token", "")
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
if err != nil {
log.Err(err).Msg("failed to get attachment")
server.RespondServerError(w)
return
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title))
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(w, r, doc.Path)
}
}
// HandleItemAttachmentToken godocs
// @Summary retrieves an attachment for an item
// @Tags Items
// @Produce application/octet-stream
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 200 {object} ItemAttachmentToken
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentDelete godocs
// @Summary retrieves an attachment for an item
// @Tags Items
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 204
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentUpdate godocs
// @Summary retrieves an attachment for an item
// @Tags Items
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
attachmentId, err := uuid.Parse(chi.URLParam(r, "attachment_id"))
if err != nil {
log.Err(err).Msg("failed to parse attachment_id param")
server.RespondError(w, http.StatusBadRequest, err)
return
}
ctx := services.NewContext(r.Context())
switch r.Method {
// Token Handler
case http.MethodGet:
token, err := ctrl.svc.Items.AttachmentToken(ctx, uid, attachmentId)
if err != nil {
switch err {
case services.ErrNotFound:
log.Err(err).
Str("id", attachmentId.String()).
Msg("failed to find attachment with id")
server.RespondError(w, http.StatusNotFound, err)
case services.ErrFileNotFound:
log.Err(err).
Str("id", attachmentId.String()).
Msg("failed to find file path for attachment with id")
log.Warn().Msg("attachment with no file path removed from database")
server.RespondError(w, http.StatusNotFound, err)
default:
log.Err(err).Msg("failed to get attachment")
server.RespondServerError(w)
return
}
}
server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
// Delete Attachment Handler
case http.MethodDelete:
err = ctrl.svc.Items.AttachmentDelete(r.Context(), user.GroupID, uid, attachmentId)
if err != nil {
log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
// Update Attachment Handler
case http.MethodPut:
var attachment repo.ItemAttachmentUpdate
err = server.Decode(r, &attachment)
if err != nil {
log.Err(err).Msg("failed to decode attachment")
server.RespondError(w, http.StatusBadRequest, err)
return
}
attachment.ID = attachmentId
val, err := ctrl.svc.Items.AttachmentUpdate(ctx, uid, &attachment)
if err != nil {
log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, val)
}
}

View file

@ -1,150 +0,0 @@
package v1
import (
"net/http"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleLabelsGetAll godoc
// @Summary Get All Labels
// @Tags Labels
// @Produce json
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
// @Router /v1/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := services.UseUserCtx(r.Context())
labels, err := ctrl.svc.Labels.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("error getting labels")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, server.Results{Items: labels})
}
}
// HandleLabelsCreate godoc
// @Summary Create a new label
// @Tags Labels
// @Produce json
// @Param payload body repo.LabelCreate true "Label Data"
// @Success 200 {object} repo.LabelSummary
// @Router /v1/labels [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := repo.LabelCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("error decoding label create data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
user := services.UseUserCtx(r.Context())
label, err := ctrl.svc.Labels.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("error creating label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusCreated, label)
}
}
// HandleLabelDelete godocs
// @Summary deletes a label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 204
// @Router /v1/labels/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
err = ctrl.svc.Labels.Delete(r.Context(), user.GroupID, uid)
if err != nil {
log.Err(err).Msg("error deleting label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
// HandleLabelGet godocs
// @Summary Gets a label and fields
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
labels, err := ctrl.svc.Labels.Get(r.Context(), user.GroupID, uid)
if err != nil {
if ent.IsNotFound(err) {
log.Err(err).
Str("id", uid.String()).
Msg("label not found")
server.RespondError(w, http.StatusNotFound, err)
return
}
log.Err(err).Msg("error getting label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, labels)
}
}
// HandleLabelUpdate godocs
// @Summary updates a label
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := repo.LabelUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("error decoding label update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
body.ID = uid
result, err := ctrl.svc.Labels.Update(r.Context(), user.GroupID, body)
if err != nil {
log.Err(err).Msg("error updating label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
}
}

View file

@ -1,157 +0,0 @@
package v1
import (
"net/http"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleLocationGetAll godoc
// @Summary Get All Locations
// @Tags Locations
// @Produce json
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
// @Router /v1/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := services.UseUserCtx(r.Context())
locations, err := ctrl.svc.Location.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("failed to get locations")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, server.Results{Items: locations})
}
}
// HandleLocationCreate godoc
// @Summary Create a new location
// @Tags Locations
// @Produce json
// @Param payload body repo.LocationCreate true "Location Data"
// @Success 200 {object} repo.LocationSummary
// @Router /v1/locations [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := repo.LocationCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode location create data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
user := services.UseUserCtx(r.Context())
location, err := ctrl.svc.Location.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("failed to create location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusCreated, location)
}
}
// HandleLocationDelete godocs
// @Summary deletes a location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 204
// @Router /v1/locations/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
err = ctrl.svc.Location.Delete(r.Context(), user.GroupID, uid)
if err != nil {
log.Err(err).Msg("failed to delete location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
// HandleLocationGet godocs
// @Summary Gets a location and fields
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
location, err := ctrl.svc.Location.GetOne(r.Context(), user.GroupID, uid)
if err != nil {
if ent.IsNotFound(err) {
log.Err(err).
Str("id", uid.String()).
Str("gid", user.GroupID.String()).
Msg("location not found")
server.RespondError(w, http.StatusNotFound, err)
return
}
log.Err(err).
Str("id", uid.String()).
Str("gid", user.GroupID.String()).
Msg("failed to get location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, location)
}
}
// HandleLocationUpdate godocs
// @Summary updates a location
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := repo.LocationUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode location update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
body.ID = uid
result, err := ctrl.svc.Location.Update(r.Context(), user.GroupID, body)
if err != nil {
log.Err(err).Msg("failed to update location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
}
}

View file

@ -1,160 +0,0 @@
package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleUserSelf godoc
// @Summary Get the current user
// @Tags User
// @Produce json
// @Param payload body services.UserRegistration true "User Data"
// @Success 204
// @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
regData := services.UserRegistration{}
if err := server.Decode(r, &regData); err != nil {
log.Err(err).Msg("failed to decode user registration data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
if err != nil {
log.Err(err).Msg("failed to register user")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
// HandleUserSelf godoc
// @Summary Get the current user
// @Tags User
// @Produce json
// @Success 200 {object} server.Result{item=repo.UserOut}
// @Router /v1/users/self [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := services.UseTokenCtx(r.Context())
usr, err := ctrl.svc.User.GetSelf(r.Context(), token)
if usr.ID == uuid.Nil || err != nil {
log.Err(err).Msg("failed to get user")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, server.Wrap(usr))
}
}
// HandleUserSelfUpdate godoc
// @Summary Update the current user
// @Tags User
// @Produce json
// @Param payload body repo.UserUpdate true "User Data"
// @Success 200 {object} server.Result{item=repo.UserUpdate}
// @Router /v1/users/self [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
updateData := repo.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil {
log.Err(err).Msg("failed to decode user update data")
server.RespondError(w, http.StatusBadRequest, err)
return
}
actor := services.UseUserCtx(r.Context())
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
if err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusOK, server.Wrap(newData))
}
}
// HandleUserUpdatePassword godoc
// @Summary Update the current user's password // TODO:
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self/password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserUpdatePassword() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
}
}
// HandleUserSelfDelete godoc
// @Summary Deletes the user account
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
actor := services.UseUserCtx(r.Context())
if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
type (
ChangePassword struct {
Current string `json:"current,omitempty"`
New string `json:"new,omitempty"`
}
)
// HandleUserSelfChangePassword godoc
// @Summary Updates the users password
// @Tags User
// @Success 204
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfChangePassword() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if ctrl.isDemo {
server.RespondError(w, http.StatusForbidden, nil)
return
}
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.ChangePassword(ctx, cp.Current, cp.New)
if !ok {
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}

View file

@ -2,10 +2,11 @@ package main
import (
"context"
"fmt"
"log"
"os"
"github.com/hay-kot/homebox/backend/ent/migrate"
"github.com/hay-kot/homebox/backend/internal/data/ent/migrate"
atlas "ariga.io/atlas/sql/migrate"
_ "ariga.io/atlas/sql/sqlite"
@ -17,7 +18,7 @@ import (
func main() {
ctx := context.Background()
// Create a local migration directory able to understand Atlas migration file format for replay.
dir, err := atlas.NewLocalDir("internal/migrations/migrations")
dir, err := atlas.NewLocalDir("internal/data/migrations/migrations")
if err != nil {
log.Fatalf("failed creating atlas migration directory: %v", err)
}
@ -39,4 +40,6 @@ func main() {
if err != nil {
log.Fatalf("failed generating migration file: %v", err)
}
fmt.Println("Migration file generated successfully.")
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,190 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
)
// DocumentToken is the model entity for the DocumentToken schema.
type DocumentToken struct {
config `json:"-"`
// ID of the ent.
ID uuid.UUID `json:"id,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// Token holds the value of the "token" field.
Token []byte `json:"token,omitempty"`
// Uses holds the value of the "uses" field.
Uses int `json:"uses,omitempty"`
// ExpiresAt holds the value of the "expires_at" field.
ExpiresAt time.Time `json:"expires_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the DocumentTokenQuery when eager-loading is set.
Edges DocumentTokenEdges `json:"edges"`
document_document_tokens *uuid.UUID
}
// DocumentTokenEdges holds the relations/edges for other nodes in the graph.
type DocumentTokenEdges struct {
// Document holds the value of the document edge.
Document *Document `json:"document,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// DocumentOrErr returns the Document value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e DocumentTokenEdges) DocumentOrErr() (*Document, error) {
if e.loadedTypes[0] {
if e.Document == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: document.Label}
}
return e.Document, nil
}
return nil, &NotLoadedError{edge: "document"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*DocumentToken) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case documenttoken.FieldToken:
values[i] = new([]byte)
case documenttoken.FieldUses:
values[i] = new(sql.NullInt64)
case documenttoken.FieldCreatedAt, documenttoken.FieldUpdatedAt, documenttoken.FieldExpiresAt:
values[i] = new(sql.NullTime)
case documenttoken.FieldID:
values[i] = new(uuid.UUID)
case documenttoken.ForeignKeys[0]: // document_document_tokens
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
return nil, fmt.Errorf("unexpected column %q for type DocumentToken", columns[i])
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the DocumentToken fields.
func (dt *DocumentToken) 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 documenttoken.FieldID:
if value, ok := values[i].(*uuid.UUID); !ok {
return fmt.Errorf("unexpected type %T for field id", values[i])
} else if value != nil {
dt.ID = *value
}
case documenttoken.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 {
dt.CreatedAt = value.Time
}
case documenttoken.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 {
dt.UpdatedAt = value.Time
}
case documenttoken.FieldToken:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field token", values[i])
} else if value != nil {
dt.Token = *value
}
case documenttoken.FieldUses:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field uses", values[i])
} else if value.Valid {
dt.Uses = int(value.Int64)
}
case documenttoken.FieldExpiresAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field expires_at", values[i])
} else if value.Valid {
dt.ExpiresAt = value.Time
}
case documenttoken.ForeignKeys[0]:
if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field document_document_tokens", values[i])
} else if value.Valid {
dt.document_document_tokens = new(uuid.UUID)
*dt.document_document_tokens = *value.S.(*uuid.UUID)
}
}
}
return nil
}
// QueryDocument queries the "document" edge of the DocumentToken entity.
func (dt *DocumentToken) QueryDocument() *DocumentQuery {
return (&DocumentTokenClient{config: dt.config}).QueryDocument(dt)
}
// Update returns a builder for updating this DocumentToken.
// Note that you need to call DocumentToken.Unwrap() before calling this method if this DocumentToken
// was returned from a transaction, and the transaction was committed or rolled back.
func (dt *DocumentToken) Update() *DocumentTokenUpdateOne {
return (&DocumentTokenClient{config: dt.config}).UpdateOne(dt)
}
// Unwrap unwraps the DocumentToken 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 (dt *DocumentToken) Unwrap() *DocumentToken {
_tx, ok := dt.config.driver.(*txDriver)
if !ok {
panic("ent: DocumentToken is not a transactional entity")
}
dt.config.driver = _tx.drv
return dt
}
// String implements the fmt.Stringer.
func (dt *DocumentToken) String() string {
var builder strings.Builder
builder.WriteString("DocumentToken(")
builder.WriteString(fmt.Sprintf("id=%v, ", dt.ID))
builder.WriteString("created_at=")
builder.WriteString(dt.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(dt.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("token=")
builder.WriteString(fmt.Sprintf("%v", dt.Token))
builder.WriteString(", ")
builder.WriteString("uses=")
builder.WriteString(fmt.Sprintf("%v", dt.Uses))
builder.WriteString(", ")
builder.WriteString("expires_at=")
builder.WriteString(dt.ExpiresAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// DocumentTokens is a parsable slice of DocumentToken.
type DocumentTokens []*DocumentToken
func (dt DocumentTokens) config(cfg config) {
for _i := range dt {
dt[_i].config = cfg
}
}

View file

@ -1,85 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package documenttoken
import (
"time"
"github.com/google/uuid"
)
const (
// Label holds the string label denoting the documenttoken type in the database.
Label = "document_token"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldToken holds the string denoting the token field in the database.
FieldToken = "token"
// FieldUses holds the string denoting the uses field in the database.
FieldUses = "uses"
// FieldExpiresAt holds the string denoting the expires_at field in the database.
FieldExpiresAt = "expires_at"
// EdgeDocument holds the string denoting the document edge name in mutations.
EdgeDocument = "document"
// Table holds the table name of the documenttoken in the database.
Table = "document_tokens"
// DocumentTable is the table that holds the document relation/edge.
DocumentTable = "document_tokens"
// DocumentInverseTable is the table name for the Document entity.
// It exists in this package in order to avoid circular dependency with the "document" package.
DocumentInverseTable = "documents"
// DocumentColumn is the table column denoting the document relation/edge.
DocumentColumn = "document_document_tokens"
)
// Columns holds all SQL columns for documenttoken fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldToken,
FieldUses,
FieldExpiresAt,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "document_tokens"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
"document_document_tokens",
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
for i := range ForeignKeys {
if column == ForeignKeys[i] {
return true
}
}
return false
}
var (
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
// TokenValidator is a validator for the "token" field. It is called by the builders before save.
TokenValidator func([]byte) error
// DefaultUses holds the default value on creation for the "uses" field.
DefaultUses int
// DefaultExpiresAt holds the default value on creation for the "expires_at" field.
DefaultExpiresAt func() time.Time
// DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID
)

View file

@ -1,498 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package documenttoken
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldID), id))
})
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.In(s.C(FieldID), v...))
})
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.NotIn(s.C(FieldID), v...))
})
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldID), id))
})
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldID), id))
})
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldID), id))
})
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldID), id))
})
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// Token applies equality check predicate on the "token" field. It's identical to TokenEQ.
func Token(v []byte) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldToken), v))
})
}
// Uses applies equality check predicate on the "uses" field. It's identical to UsesEQ.
func Uses(v int) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUses), v))
})
}
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
func ExpiresAt(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldExpiresAt), v))
})
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldCreatedAt), v))
})
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldUpdatedAt), v))
})
}
// TokenEQ applies the EQ predicate on the "token" field.
func TokenEQ(v []byte) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldToken), v))
})
}
// TokenNEQ applies the NEQ predicate on the "token" field.
func TokenNEQ(v []byte) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldToken), v))
})
}
// TokenIn applies the In predicate on the "token" field.
func TokenIn(vs ...[]byte) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldToken), v...))
})
}
// TokenNotIn applies the NotIn predicate on the "token" field.
func TokenNotIn(vs ...[]byte) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldToken), v...))
})
}
// TokenGT applies the GT predicate on the "token" field.
func TokenGT(v []byte) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldToken), v))
})
}
// TokenGTE applies the GTE predicate on the "token" field.
func TokenGTE(v []byte) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldToken), v))
})
}
// TokenLT applies the LT predicate on the "token" field.
func TokenLT(v []byte) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldToken), v))
})
}
// TokenLTE applies the LTE predicate on the "token" field.
func TokenLTE(v []byte) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldToken), v))
})
}
// UsesEQ applies the EQ predicate on the "uses" field.
func UsesEQ(v int) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUses), v))
})
}
// UsesNEQ applies the NEQ predicate on the "uses" field.
func UsesNEQ(v int) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldUses), v))
})
}
// UsesIn applies the In predicate on the "uses" field.
func UsesIn(vs ...int) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldUses), v...))
})
}
// UsesNotIn applies the NotIn predicate on the "uses" field.
func UsesNotIn(vs ...int) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldUses), v...))
})
}
// UsesGT applies the GT predicate on the "uses" field.
func UsesGT(v int) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldUses), v))
})
}
// UsesGTE applies the GTE predicate on the "uses" field.
func UsesGTE(v int) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldUses), v))
})
}
// UsesLT applies the LT predicate on the "uses" field.
func UsesLT(v int) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldUses), v))
})
}
// UsesLTE applies the LTE predicate on the "uses" field.
func UsesLTE(v int) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldUses), v))
})
}
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
func ExpiresAtEQ(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
func ExpiresAtNEQ(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtIn applies the In predicate on the "expires_at" field.
func ExpiresAtIn(vs ...time.Time) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldExpiresAt), v...))
})
}
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
func ExpiresAtNotIn(vs ...time.Time) predicate.DocumentToken {
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldExpiresAt), v...))
})
}
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
func ExpiresAtGT(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
func ExpiresAtGTE(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
func ExpiresAtLT(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
func ExpiresAtLTE(v time.Time) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldExpiresAt), v))
})
}
// HasDocument applies the HasEdge predicate on the "document" edge.
func HasDocument() predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasDocumentWith applies the HasEdge predicate on the "document" edge with a given conditions (other predicates).
func HasDocumentWith(preds ...predicate.Document) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
)
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.DocumentToken) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for _, p := range predicates {
p(s1)
}
s.Where(s1.P())
})
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.DocumentToken) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for i, p := range predicates {
if i > 0 {
s1.Or()
}
p(s1)
}
s.Where(s1.P())
})
}
// Not applies the not operator on the given predicate.
func Not(p predicate.DocumentToken) predicate.DocumentToken {
return predicate.DocumentToken(func(s *sql.Selector) {
p(s.Not())
})
}

View file

@ -1,418 +0,0 @@
// 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/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
)
// DocumentTokenCreate is the builder for creating a DocumentToken entity.
type DocumentTokenCreate struct {
config
mutation *DocumentTokenMutation
hooks []Hook
}
// SetCreatedAt sets the "created_at" field.
func (dtc *DocumentTokenCreate) SetCreatedAt(t time.Time) *DocumentTokenCreate {
dtc.mutation.SetCreatedAt(t)
return dtc
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (dtc *DocumentTokenCreate) SetNillableCreatedAt(t *time.Time) *DocumentTokenCreate {
if t != nil {
dtc.SetCreatedAt(*t)
}
return dtc
}
// SetUpdatedAt sets the "updated_at" field.
func (dtc *DocumentTokenCreate) SetUpdatedAt(t time.Time) *DocumentTokenCreate {
dtc.mutation.SetUpdatedAt(t)
return dtc
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (dtc *DocumentTokenCreate) SetNillableUpdatedAt(t *time.Time) *DocumentTokenCreate {
if t != nil {
dtc.SetUpdatedAt(*t)
}
return dtc
}
// SetToken sets the "token" field.
func (dtc *DocumentTokenCreate) SetToken(b []byte) *DocumentTokenCreate {
dtc.mutation.SetToken(b)
return dtc
}
// SetUses sets the "uses" field.
func (dtc *DocumentTokenCreate) SetUses(i int) *DocumentTokenCreate {
dtc.mutation.SetUses(i)
return dtc
}
// SetNillableUses sets the "uses" field if the given value is not nil.
func (dtc *DocumentTokenCreate) SetNillableUses(i *int) *DocumentTokenCreate {
if i != nil {
dtc.SetUses(*i)
}
return dtc
}
// SetExpiresAt sets the "expires_at" field.
func (dtc *DocumentTokenCreate) SetExpiresAt(t time.Time) *DocumentTokenCreate {
dtc.mutation.SetExpiresAt(t)
return dtc
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (dtc *DocumentTokenCreate) SetNillableExpiresAt(t *time.Time) *DocumentTokenCreate {
if t != nil {
dtc.SetExpiresAt(*t)
}
return dtc
}
// SetID sets the "id" field.
func (dtc *DocumentTokenCreate) SetID(u uuid.UUID) *DocumentTokenCreate {
dtc.mutation.SetID(u)
return dtc
}
// SetNillableID sets the "id" field if the given value is not nil.
func (dtc *DocumentTokenCreate) SetNillableID(u *uuid.UUID) *DocumentTokenCreate {
if u != nil {
dtc.SetID(*u)
}
return dtc
}
// SetDocumentID sets the "document" edge to the Document entity by ID.
func (dtc *DocumentTokenCreate) SetDocumentID(id uuid.UUID) *DocumentTokenCreate {
dtc.mutation.SetDocumentID(id)
return dtc
}
// SetNillableDocumentID sets the "document" edge to the Document entity by ID if the given value is not nil.
func (dtc *DocumentTokenCreate) SetNillableDocumentID(id *uuid.UUID) *DocumentTokenCreate {
if id != nil {
dtc = dtc.SetDocumentID(*id)
}
return dtc
}
// SetDocument sets the "document" edge to the Document entity.
func (dtc *DocumentTokenCreate) SetDocument(d *Document) *DocumentTokenCreate {
return dtc.SetDocumentID(d.ID)
}
// Mutation returns the DocumentTokenMutation object of the builder.
func (dtc *DocumentTokenCreate) Mutation() *DocumentTokenMutation {
return dtc.mutation
}
// Save creates the DocumentToken in the database.
func (dtc *DocumentTokenCreate) Save(ctx context.Context) (*DocumentToken, error) {
var (
err error
node *DocumentToken
)
dtc.defaults()
if len(dtc.hooks) == 0 {
if err = dtc.check(); err != nil {
return nil, err
}
node, err = dtc.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentTokenMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = dtc.check(); err != nil {
return nil, err
}
dtc.mutation = mutation
if node, err = dtc.sqlSave(ctx); err != nil {
return nil, err
}
mutation.id = &node.ID
mutation.done = true
return node, err
})
for i := len(dtc.hooks) - 1; i >= 0; i-- {
if dtc.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = dtc.hooks[i](mut)
}
v, err := mut.Mutate(ctx, dtc.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*DocumentToken)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from DocumentTokenMutation", v)
}
node = nv
}
return node, err
}
// SaveX calls Save and panics if Save returns an error.
func (dtc *DocumentTokenCreate) SaveX(ctx context.Context) *DocumentToken {
v, err := dtc.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (dtc *DocumentTokenCreate) Exec(ctx context.Context) error {
_, err := dtc.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (dtc *DocumentTokenCreate) ExecX(ctx context.Context) {
if err := dtc.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (dtc *DocumentTokenCreate) defaults() {
if _, ok := dtc.mutation.CreatedAt(); !ok {
v := documenttoken.DefaultCreatedAt()
dtc.mutation.SetCreatedAt(v)
}
if _, ok := dtc.mutation.UpdatedAt(); !ok {
v := documenttoken.DefaultUpdatedAt()
dtc.mutation.SetUpdatedAt(v)
}
if _, ok := dtc.mutation.Uses(); !ok {
v := documenttoken.DefaultUses
dtc.mutation.SetUses(v)
}
if _, ok := dtc.mutation.ExpiresAt(); !ok {
v := documenttoken.DefaultExpiresAt()
dtc.mutation.SetExpiresAt(v)
}
if _, ok := dtc.mutation.ID(); !ok {
v := documenttoken.DefaultID()
dtc.mutation.SetID(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (dtc *DocumentTokenCreate) check() error {
if _, ok := dtc.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "DocumentToken.created_at"`)}
}
if _, ok := dtc.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "DocumentToken.updated_at"`)}
}
if _, ok := dtc.mutation.Token(); !ok {
return &ValidationError{Name: "token", err: errors.New(`ent: missing required field "DocumentToken.token"`)}
}
if v, ok := dtc.mutation.Token(); ok {
if err := documenttoken.TokenValidator(v); err != nil {
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "DocumentToken.token": %w`, err)}
}
}
if _, ok := dtc.mutation.Uses(); !ok {
return &ValidationError{Name: "uses", err: errors.New(`ent: missing required field "DocumentToken.uses"`)}
}
if _, ok := dtc.mutation.ExpiresAt(); !ok {
return &ValidationError{Name: "expires_at", err: errors.New(`ent: missing required field "DocumentToken.expires_at"`)}
}
return nil
}
func (dtc *DocumentTokenCreate) sqlSave(ctx context.Context) (*DocumentToken, error) {
_node, _spec := dtc.createSpec()
if err := sqlgraph.CreateNode(ctx, dtc.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
}
}
return _node, nil
}
func (dtc *DocumentTokenCreate) createSpec() (*DocumentToken, *sqlgraph.CreateSpec) {
var (
_node = &DocumentToken{config: dtc.config}
_spec = &sqlgraph.CreateSpec{
Table: documenttoken.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
}
)
if id, ok := dtc.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = &id
}
if value, ok := dtc.mutation.CreatedAt(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: documenttoken.FieldCreatedAt,
})
_node.CreatedAt = value
}
if value, ok := dtc.mutation.UpdatedAt(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: documenttoken.FieldUpdatedAt,
})
_node.UpdatedAt = value
}
if value, ok := dtc.mutation.Token(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeBytes,
Value: value,
Column: documenttoken.FieldToken,
})
_node.Token = value
}
if value, ok := dtc.mutation.Uses(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeInt,
Value: value,
Column: documenttoken.FieldUses,
})
_node.Uses = value
}
if value, ok := dtc.mutation.ExpiresAt(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: documenttoken.FieldExpiresAt,
})
_node.ExpiresAt = value
}
if nodes := dtc.mutation.DocumentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: documenttoken.DocumentTable,
Columns: []string{documenttoken.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.document_document_tokens = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// DocumentTokenCreateBulk is the builder for creating many DocumentToken entities in bulk.
type DocumentTokenCreateBulk struct {
config
builders []*DocumentTokenCreate
}
// Save creates the DocumentToken entities in the database.
func (dtcb *DocumentTokenCreateBulk) Save(ctx context.Context) ([]*DocumentToken, error) {
specs := make([]*sqlgraph.CreateSpec, len(dtcb.builders))
nodes := make([]*DocumentToken, len(dtcb.builders))
mutators := make([]Mutator, len(dtcb.builders))
for i := range dtcb.builders {
func(i int, root context.Context) {
builder := dtcb.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentTokenMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, dtcb.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, dtcb.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, dtcb.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (dtcb *DocumentTokenCreateBulk) SaveX(ctx context.Context) []*DocumentToken {
v, err := dtcb.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (dtcb *DocumentTokenCreateBulk) Exec(ctx context.Context) error {
_, err := dtcb.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (dtcb *DocumentTokenCreateBulk) ExecX(ctx context.Context) {
if err := dtcb.Exec(ctx); err != nil {
panic(err)
}
}

View file

@ -1,115 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentTokenDelete is the builder for deleting a DocumentToken entity.
type DocumentTokenDelete struct {
config
hooks []Hook
mutation *DocumentTokenMutation
}
// Where appends a list predicates to the DocumentTokenDelete builder.
func (dtd *DocumentTokenDelete) Where(ps ...predicate.DocumentToken) *DocumentTokenDelete {
dtd.mutation.Where(ps...)
return dtd
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (dtd *DocumentTokenDelete) Exec(ctx context.Context) (int, error) {
var (
err error
affected int
)
if len(dtd.hooks) == 0 {
affected, err = dtd.sqlExec(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentTokenMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
dtd.mutation = mutation
affected, err = dtd.sqlExec(ctx)
mutation.done = true
return affected, err
})
for i := len(dtd.hooks) - 1; i >= 0; i-- {
if dtd.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = dtd.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, dtd.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// ExecX is like Exec, but panics if an error occurs.
func (dtd *DocumentTokenDelete) ExecX(ctx context.Context) int {
n, err := dtd.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (dtd *DocumentTokenDelete) sqlExec(ctx context.Context) (int, error) {
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: documenttoken.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
if ps := dtd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, dtd.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return affected, err
}
// DocumentTokenDeleteOne is the builder for deleting a single DocumentToken entity.
type DocumentTokenDeleteOne struct {
dtd *DocumentTokenDelete
}
// Exec executes the deletion query.
func (dtdo *DocumentTokenDeleteOne) Exec(ctx context.Context) error {
n, err := dtdo.dtd.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{documenttoken.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (dtdo *DocumentTokenDeleteOne) ExecX(ctx context.Context) {
dtdo.dtd.ExecX(ctx)
}

View file

@ -1,614 +0,0 @@
// 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/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentTokenQuery is the builder for querying DocumentToken entities.
type DocumentTokenQuery struct {
config
limit *int
offset *int
unique *bool
order []OrderFunc
fields []string
predicates []predicate.DocumentToken
withDocument *DocumentQuery
withFKs bool
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the DocumentTokenQuery builder.
func (dtq *DocumentTokenQuery) Where(ps ...predicate.DocumentToken) *DocumentTokenQuery {
dtq.predicates = append(dtq.predicates, ps...)
return dtq
}
// Limit adds a limit step to the query.
func (dtq *DocumentTokenQuery) Limit(limit int) *DocumentTokenQuery {
dtq.limit = &limit
return dtq
}
// Offset adds an offset step to the query.
func (dtq *DocumentTokenQuery) Offset(offset int) *DocumentTokenQuery {
dtq.offset = &offset
return dtq
}
// 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 (dtq *DocumentTokenQuery) Unique(unique bool) *DocumentTokenQuery {
dtq.unique = &unique
return dtq
}
// Order adds an order step to the query.
func (dtq *DocumentTokenQuery) Order(o ...OrderFunc) *DocumentTokenQuery {
dtq.order = append(dtq.order, o...)
return dtq
}
// QueryDocument chains the current query on the "document" edge.
func (dtq *DocumentTokenQuery) QueryDocument() *DocumentQuery {
query := &DocumentQuery{config: dtq.config}
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := dtq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := dtq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(documenttoken.Table, documenttoken.FieldID, selector),
sqlgraph.To(document.Table, document.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, documenttoken.DocumentTable, documenttoken.DocumentColumn),
)
fromU = sqlgraph.SetNeighbors(dtq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first DocumentToken entity from the query.
// Returns a *NotFoundError when no DocumentToken was found.
func (dtq *DocumentTokenQuery) First(ctx context.Context) (*DocumentToken, error) {
nodes, err := dtq.Limit(1).All(ctx)
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{documenttoken.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (dtq *DocumentTokenQuery) FirstX(ctx context.Context) *DocumentToken {
node, err := dtq.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first DocumentToken ID from the query.
// Returns a *NotFoundError when no DocumentToken ID was found.
func (dtq *DocumentTokenQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dtq.Limit(1).IDs(ctx); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{documenttoken.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (dtq *DocumentTokenQuery) FirstIDX(ctx context.Context) uuid.UUID {
id, err := dtq.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single DocumentToken entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one DocumentToken entity is found.
// Returns a *NotFoundError when no DocumentToken entities are found.
func (dtq *DocumentTokenQuery) Only(ctx context.Context) (*DocumentToken, error) {
nodes, err := dtq.Limit(2).All(ctx)
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{documenttoken.Label}
default:
return nil, &NotSingularError{documenttoken.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (dtq *DocumentTokenQuery) OnlyX(ctx context.Context) *DocumentToken {
node, err := dtq.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only DocumentToken ID in the query.
// Returns a *NotSingularError when more than one DocumentToken ID is found.
// Returns a *NotFoundError when no entities are found.
func (dtq *DocumentTokenQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dtq.Limit(2).IDs(ctx); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{documenttoken.Label}
default:
err = &NotSingularError{documenttoken.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (dtq *DocumentTokenQuery) OnlyIDX(ctx context.Context) uuid.UUID {
id, err := dtq.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of DocumentTokens.
func (dtq *DocumentTokenQuery) All(ctx context.Context) ([]*DocumentToken, error) {
if err := dtq.prepareQuery(ctx); err != nil {
return nil, err
}
return dtq.sqlAll(ctx)
}
// AllX is like All, but panics if an error occurs.
func (dtq *DocumentTokenQuery) AllX(ctx context.Context) []*DocumentToken {
nodes, err := dtq.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of DocumentToken IDs.
func (dtq *DocumentTokenQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
if err := dtq.Select(documenttoken.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (dtq *DocumentTokenQuery) IDsX(ctx context.Context) []uuid.UUID {
ids, err := dtq.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (dtq *DocumentTokenQuery) Count(ctx context.Context) (int, error) {
if err := dtq.prepareQuery(ctx); err != nil {
return 0, err
}
return dtq.sqlCount(ctx)
}
// CountX is like Count, but panics if an error occurs.
func (dtq *DocumentTokenQuery) CountX(ctx context.Context) int {
count, err := dtq.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (dtq *DocumentTokenQuery) Exist(ctx context.Context) (bool, error) {
if err := dtq.prepareQuery(ctx); err != nil {
return false, err
}
return dtq.sqlExist(ctx)
}
// ExistX is like Exist, but panics if an error occurs.
func (dtq *DocumentTokenQuery) ExistX(ctx context.Context) bool {
exist, err := dtq.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the DocumentTokenQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (dtq *DocumentTokenQuery) Clone() *DocumentTokenQuery {
if dtq == nil {
return nil
}
return &DocumentTokenQuery{
config: dtq.config,
limit: dtq.limit,
offset: dtq.offset,
order: append([]OrderFunc{}, dtq.order...),
predicates: append([]predicate.DocumentToken{}, dtq.predicates...),
withDocument: dtq.withDocument.Clone(),
// clone intermediate query.
sql: dtq.sql.Clone(),
path: dtq.path,
unique: dtq.unique,
}
}
// WithDocument tells the query-builder to eager-load the nodes that are connected to
// the "document" edge. The optional arguments are used to configure the query builder of the edge.
func (dtq *DocumentTokenQuery) WithDocument(opts ...func(*DocumentQuery)) *DocumentTokenQuery {
query := &DocumentQuery{config: dtq.config}
for _, opt := range opts {
opt(query)
}
dtq.withDocument = query
return dtq
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.DocumentToken.Query().
// GroupBy(documenttoken.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (dtq *DocumentTokenQuery) GroupBy(field string, fields ...string) *DocumentTokenGroupBy {
grbuild := &DocumentTokenGroupBy{config: dtq.config}
grbuild.fields = append([]string{field}, fields...)
grbuild.path = func(ctx context.Context) (prev *sql.Selector, err error) {
if err := dtq.prepareQuery(ctx); err != nil {
return nil, err
}
return dtq.sqlQuery(ctx), nil
}
grbuild.label = documenttoken.Label
grbuild.flds, grbuild.scan = &grbuild.fields, grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// CreatedAt time.Time `json:"created_at,omitempty"`
// }
//
// client.DocumentToken.Query().
// Select(documenttoken.FieldCreatedAt).
// Scan(ctx, &v)
func (dtq *DocumentTokenQuery) Select(fields ...string) *DocumentTokenSelect {
dtq.fields = append(dtq.fields, fields...)
selbuild := &DocumentTokenSelect{DocumentTokenQuery: dtq}
selbuild.label = documenttoken.Label
selbuild.flds, selbuild.scan = &dtq.fields, selbuild.Scan
return selbuild
}
func (dtq *DocumentTokenQuery) prepareQuery(ctx context.Context) error {
for _, f := range dtq.fields {
if !documenttoken.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if dtq.path != nil {
prev, err := dtq.path(ctx)
if err != nil {
return err
}
dtq.sql = prev
}
return nil
}
func (dtq *DocumentTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*DocumentToken, error) {
var (
nodes = []*DocumentToken{}
withFKs = dtq.withFKs
_spec = dtq.querySpec()
loadedTypes = [1]bool{
dtq.withDocument != nil,
}
)
if dtq.withDocument != nil {
withFKs = true
}
if withFKs {
_spec.Node.Columns = append(_spec.Node.Columns, documenttoken.ForeignKeys...)
}
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*DocumentToken).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &DocumentToken{config: dtq.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, dtq.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := dtq.withDocument; query != nil {
if err := dtq.loadDocument(ctx, query, nodes, nil,
func(n *DocumentToken, e *Document) { n.Edges.Document = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (dtq *DocumentTokenQuery) loadDocument(ctx context.Context, query *DocumentQuery, nodes []*DocumentToken, init func(*DocumentToken), assign func(*DocumentToken, *Document)) error {
ids := make([]uuid.UUID, 0, len(nodes))
nodeids := make(map[uuid.UUID][]*DocumentToken)
for i := range nodes {
if nodes[i].document_document_tokens == nil {
continue
}
fk := *nodes[i].document_document_tokens
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
query.Where(document.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 "document_document_tokens" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (dtq *DocumentTokenQuery) sqlCount(ctx context.Context) (int, error) {
_spec := dtq.querySpec()
_spec.Node.Columns = dtq.fields
if len(dtq.fields) > 0 {
_spec.Unique = dtq.unique != nil && *dtq.unique
}
return sqlgraph.CountNodes(ctx, dtq.driver, _spec)
}
func (dtq *DocumentTokenQuery) sqlExist(ctx context.Context) (bool, error) {
switch _, err := dtq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
func (dtq *DocumentTokenQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: documenttoken.Table,
Columns: documenttoken.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
From: dtq.sql,
Unique: true,
}
if unique := dtq.unique; unique != nil {
_spec.Unique = *unique
}
if fields := dtq.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, documenttoken.FieldID)
for i := range fields {
if fields[i] != documenttoken.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := dtq.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := dtq.limit; limit != nil {
_spec.Limit = *limit
}
if offset := dtq.offset; offset != nil {
_spec.Offset = *offset
}
if ps := dtq.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (dtq *DocumentTokenQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(dtq.driver.Dialect())
t1 := builder.Table(documenttoken.Table)
columns := dtq.fields
if len(columns) == 0 {
columns = documenttoken.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if dtq.sql != nil {
selector = dtq.sql
selector.Select(selector.Columns(columns...)...)
}
if dtq.unique != nil && *dtq.unique {
selector.Distinct()
}
for _, p := range dtq.predicates {
p(selector)
}
for _, p := range dtq.order {
p(selector)
}
if offset := dtq.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 := dtq.limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// DocumentTokenGroupBy is the group-by builder for DocumentToken entities.
type DocumentTokenGroupBy struct {
config
selector
fields []string
fns []AggregateFunc
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Aggregate adds the given aggregation functions to the group-by query.
func (dtgb *DocumentTokenGroupBy) Aggregate(fns ...AggregateFunc) *DocumentTokenGroupBy {
dtgb.fns = append(dtgb.fns, fns...)
return dtgb
}
// Scan applies the group-by query and scans the result into the given value.
func (dtgb *DocumentTokenGroupBy) Scan(ctx context.Context, v any) error {
query, err := dtgb.path(ctx)
if err != nil {
return err
}
dtgb.sql = query
return dtgb.sqlScan(ctx, v)
}
func (dtgb *DocumentTokenGroupBy) sqlScan(ctx context.Context, v any) error {
for _, f := range dtgb.fields {
if !documenttoken.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)}
}
}
selector := dtgb.sqlQuery()
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := dtgb.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
func (dtgb *DocumentTokenGroupBy) sqlQuery() *sql.Selector {
selector := dtgb.sql.Select()
aggregation := make([]string, 0, len(dtgb.fns))
for _, fn := range dtgb.fns {
aggregation = append(aggregation, fn(selector))
}
// If no columns were selected in a custom aggregation function, the default
// selection is the fields used for "group-by", and the aggregation functions.
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(dtgb.fields)+len(dtgb.fns))
for _, f := range dtgb.fields {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
return selector.GroupBy(selector.Columns(dtgb.fields...)...)
}
// DocumentTokenSelect is the builder for selecting fields of DocumentToken entities.
type DocumentTokenSelect struct {
*DocumentTokenQuery
selector
// intermediate query (i.e. traversal path).
sql *sql.Selector
}
// Scan applies the selector query and scans the result into the given value.
func (dts *DocumentTokenSelect) Scan(ctx context.Context, v any) error {
if err := dts.prepareQuery(ctx); err != nil {
return err
}
dts.sql = dts.DocumentTokenQuery.sqlQuery(ctx)
return dts.sqlScan(ctx, v)
}
func (dts *DocumentTokenSelect) sqlScan(ctx context.Context, v any) error {
rows := &sql.Rows{}
query, args := dts.sql.Query()
if err := dts.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View file

@ -1,582 +0,0 @@
// 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/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentTokenUpdate is the builder for updating DocumentToken entities.
type DocumentTokenUpdate struct {
config
hooks []Hook
mutation *DocumentTokenMutation
}
// Where appends a list predicates to the DocumentTokenUpdate builder.
func (dtu *DocumentTokenUpdate) Where(ps ...predicate.DocumentToken) *DocumentTokenUpdate {
dtu.mutation.Where(ps...)
return dtu
}
// SetUpdatedAt sets the "updated_at" field.
func (dtu *DocumentTokenUpdate) SetUpdatedAt(t time.Time) *DocumentTokenUpdate {
dtu.mutation.SetUpdatedAt(t)
return dtu
}
// SetToken sets the "token" field.
func (dtu *DocumentTokenUpdate) SetToken(b []byte) *DocumentTokenUpdate {
dtu.mutation.SetToken(b)
return dtu
}
// SetUses sets the "uses" field.
func (dtu *DocumentTokenUpdate) SetUses(i int) *DocumentTokenUpdate {
dtu.mutation.ResetUses()
dtu.mutation.SetUses(i)
return dtu
}
// SetNillableUses sets the "uses" field if the given value is not nil.
func (dtu *DocumentTokenUpdate) SetNillableUses(i *int) *DocumentTokenUpdate {
if i != nil {
dtu.SetUses(*i)
}
return dtu
}
// AddUses adds i to the "uses" field.
func (dtu *DocumentTokenUpdate) AddUses(i int) *DocumentTokenUpdate {
dtu.mutation.AddUses(i)
return dtu
}
// SetExpiresAt sets the "expires_at" field.
func (dtu *DocumentTokenUpdate) SetExpiresAt(t time.Time) *DocumentTokenUpdate {
dtu.mutation.SetExpiresAt(t)
return dtu
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (dtu *DocumentTokenUpdate) SetNillableExpiresAt(t *time.Time) *DocumentTokenUpdate {
if t != nil {
dtu.SetExpiresAt(*t)
}
return dtu
}
// SetDocumentID sets the "document" edge to the Document entity by ID.
func (dtu *DocumentTokenUpdate) SetDocumentID(id uuid.UUID) *DocumentTokenUpdate {
dtu.mutation.SetDocumentID(id)
return dtu
}
// SetNillableDocumentID sets the "document" edge to the Document entity by ID if the given value is not nil.
func (dtu *DocumentTokenUpdate) SetNillableDocumentID(id *uuid.UUID) *DocumentTokenUpdate {
if id != nil {
dtu = dtu.SetDocumentID(*id)
}
return dtu
}
// SetDocument sets the "document" edge to the Document entity.
func (dtu *DocumentTokenUpdate) SetDocument(d *Document) *DocumentTokenUpdate {
return dtu.SetDocumentID(d.ID)
}
// Mutation returns the DocumentTokenMutation object of the builder.
func (dtu *DocumentTokenUpdate) Mutation() *DocumentTokenMutation {
return dtu.mutation
}
// ClearDocument clears the "document" edge to the Document entity.
func (dtu *DocumentTokenUpdate) ClearDocument() *DocumentTokenUpdate {
dtu.mutation.ClearDocument()
return dtu
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (dtu *DocumentTokenUpdate) Save(ctx context.Context) (int, error) {
var (
err error
affected int
)
dtu.defaults()
if len(dtu.hooks) == 0 {
if err = dtu.check(); err != nil {
return 0, err
}
affected, err = dtu.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentTokenMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = dtu.check(); err != nil {
return 0, err
}
dtu.mutation = mutation
affected, err = dtu.sqlSave(ctx)
mutation.done = true
return affected, err
})
for i := len(dtu.hooks) - 1; i >= 0; i-- {
if dtu.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = dtu.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, dtu.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// SaveX is like Save, but panics if an error occurs.
func (dtu *DocumentTokenUpdate) SaveX(ctx context.Context) int {
affected, err := dtu.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (dtu *DocumentTokenUpdate) Exec(ctx context.Context) error {
_, err := dtu.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (dtu *DocumentTokenUpdate) ExecX(ctx context.Context) {
if err := dtu.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (dtu *DocumentTokenUpdate) defaults() {
if _, ok := dtu.mutation.UpdatedAt(); !ok {
v := documenttoken.UpdateDefaultUpdatedAt()
dtu.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (dtu *DocumentTokenUpdate) check() error {
if v, ok := dtu.mutation.Token(); ok {
if err := documenttoken.TokenValidator(v); err != nil {
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "DocumentToken.token": %w`, err)}
}
}
return nil
}
func (dtu *DocumentTokenUpdate) sqlSave(ctx context.Context) (n int, err error) {
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: documenttoken.Table,
Columns: documenttoken.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
if ps := dtu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := dtu.mutation.UpdatedAt(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: documenttoken.FieldUpdatedAt,
})
}
if value, ok := dtu.mutation.Token(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeBytes,
Value: value,
Column: documenttoken.FieldToken,
})
}
if value, ok := dtu.mutation.Uses(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeInt,
Value: value,
Column: documenttoken.FieldUses,
})
}
if value, ok := dtu.mutation.AddedUses(); ok {
_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
Type: field.TypeInt,
Value: value,
Column: documenttoken.FieldUses,
})
}
if value, ok := dtu.mutation.ExpiresAt(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: documenttoken.FieldExpiresAt,
})
}
if dtu.mutation.DocumentCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: documenttoken.DocumentTable,
Columns: []string{documenttoken.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := dtu.mutation.DocumentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: documenttoken.DocumentTable,
Columns: []string{documenttoken.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
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, dtu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{documenttoken.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
return n, nil
}
// DocumentTokenUpdateOne is the builder for updating a single DocumentToken entity.
type DocumentTokenUpdateOne struct {
config
fields []string
hooks []Hook
mutation *DocumentTokenMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (dtuo *DocumentTokenUpdateOne) SetUpdatedAt(t time.Time) *DocumentTokenUpdateOne {
dtuo.mutation.SetUpdatedAt(t)
return dtuo
}
// SetToken sets the "token" field.
func (dtuo *DocumentTokenUpdateOne) SetToken(b []byte) *DocumentTokenUpdateOne {
dtuo.mutation.SetToken(b)
return dtuo
}
// SetUses sets the "uses" field.
func (dtuo *DocumentTokenUpdateOne) SetUses(i int) *DocumentTokenUpdateOne {
dtuo.mutation.ResetUses()
dtuo.mutation.SetUses(i)
return dtuo
}
// SetNillableUses sets the "uses" field if the given value is not nil.
func (dtuo *DocumentTokenUpdateOne) SetNillableUses(i *int) *DocumentTokenUpdateOne {
if i != nil {
dtuo.SetUses(*i)
}
return dtuo
}
// AddUses adds i to the "uses" field.
func (dtuo *DocumentTokenUpdateOne) AddUses(i int) *DocumentTokenUpdateOne {
dtuo.mutation.AddUses(i)
return dtuo
}
// SetExpiresAt sets the "expires_at" field.
func (dtuo *DocumentTokenUpdateOne) SetExpiresAt(t time.Time) *DocumentTokenUpdateOne {
dtuo.mutation.SetExpiresAt(t)
return dtuo
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (dtuo *DocumentTokenUpdateOne) SetNillableExpiresAt(t *time.Time) *DocumentTokenUpdateOne {
if t != nil {
dtuo.SetExpiresAt(*t)
}
return dtuo
}
// SetDocumentID sets the "document" edge to the Document entity by ID.
func (dtuo *DocumentTokenUpdateOne) SetDocumentID(id uuid.UUID) *DocumentTokenUpdateOne {
dtuo.mutation.SetDocumentID(id)
return dtuo
}
// SetNillableDocumentID sets the "document" edge to the Document entity by ID if the given value is not nil.
func (dtuo *DocumentTokenUpdateOne) SetNillableDocumentID(id *uuid.UUID) *DocumentTokenUpdateOne {
if id != nil {
dtuo = dtuo.SetDocumentID(*id)
}
return dtuo
}
// SetDocument sets the "document" edge to the Document entity.
func (dtuo *DocumentTokenUpdateOne) SetDocument(d *Document) *DocumentTokenUpdateOne {
return dtuo.SetDocumentID(d.ID)
}
// Mutation returns the DocumentTokenMutation object of the builder.
func (dtuo *DocumentTokenUpdateOne) Mutation() *DocumentTokenMutation {
return dtuo.mutation
}
// ClearDocument clears the "document" edge to the Document entity.
func (dtuo *DocumentTokenUpdateOne) ClearDocument() *DocumentTokenUpdateOne {
dtuo.mutation.ClearDocument()
return dtuo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (dtuo *DocumentTokenUpdateOne) Select(field string, fields ...string) *DocumentTokenUpdateOne {
dtuo.fields = append([]string{field}, fields...)
return dtuo
}
// Save executes the query and returns the updated DocumentToken entity.
func (dtuo *DocumentTokenUpdateOne) Save(ctx context.Context) (*DocumentToken, error) {
var (
err error
node *DocumentToken
)
dtuo.defaults()
if len(dtuo.hooks) == 0 {
if err = dtuo.check(); err != nil {
return nil, err
}
node, err = dtuo.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentTokenMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = dtuo.check(); err != nil {
return nil, err
}
dtuo.mutation = mutation
node, err = dtuo.sqlSave(ctx)
mutation.done = true
return node, err
})
for i := len(dtuo.hooks) - 1; i >= 0; i-- {
if dtuo.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = dtuo.hooks[i](mut)
}
v, err := mut.Mutate(ctx, dtuo.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*DocumentToken)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from DocumentTokenMutation", v)
}
node = nv
}
return node, err
}
// SaveX is like Save, but panics if an error occurs.
func (dtuo *DocumentTokenUpdateOne) SaveX(ctx context.Context) *DocumentToken {
node, err := dtuo.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (dtuo *DocumentTokenUpdateOne) Exec(ctx context.Context) error {
_, err := dtuo.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (dtuo *DocumentTokenUpdateOne) ExecX(ctx context.Context) {
if err := dtuo.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (dtuo *DocumentTokenUpdateOne) defaults() {
if _, ok := dtuo.mutation.UpdatedAt(); !ok {
v := documenttoken.UpdateDefaultUpdatedAt()
dtuo.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (dtuo *DocumentTokenUpdateOne) check() error {
if v, ok := dtuo.mutation.Token(); ok {
if err := documenttoken.TokenValidator(v); err != nil {
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "DocumentToken.token": %w`, err)}
}
}
return nil
}
func (dtuo *DocumentTokenUpdateOne) sqlSave(ctx context.Context) (_node *DocumentToken, err error) {
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: documenttoken.Table,
Columns: documenttoken.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
id, ok := dtuo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "DocumentToken.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := dtuo.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, documenttoken.FieldID)
for _, f := range fields {
if !documenttoken.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != documenttoken.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := dtuo.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := dtuo.mutation.UpdatedAt(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: documenttoken.FieldUpdatedAt,
})
}
if value, ok := dtuo.mutation.Token(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeBytes,
Value: value,
Column: documenttoken.FieldToken,
})
}
if value, ok := dtuo.mutation.Uses(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeInt,
Value: value,
Column: documenttoken.FieldUses,
})
}
if value, ok := dtuo.mutation.AddedUses(); ok {
_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
Type: field.TypeInt,
Value: value,
Column: documenttoken.FieldUses,
})
}
if value, ok := dtuo.mutation.ExpiresAt(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: documenttoken.FieldExpiresAt,
})
}
if dtuo.mutation.DocumentCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: documenttoken.DocumentTable,
Columns: []string{documenttoken.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := dtuo.mutation.DocumentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: documenttoken.DocumentTable,
Columns: []string{documenttoken.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &DocumentToken{config: dtuo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, dtuo.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{documenttoken.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
return _node, nil
}

View file

@ -1,3 +0,0 @@
package ent
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration ./schema

View file

@ -1,138 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package group
import (
"fmt"
"time"
"github.com/google/uuid"
)
const (
// Label holds the string label denoting the group type in the database.
Label = "group"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldCurrency holds the string denoting the currency field in the database.
FieldCurrency = "currency"
// EdgeUsers holds the string denoting the users edge name in mutations.
EdgeUsers = "users"
// EdgeLocations holds the string denoting the locations edge name in mutations.
EdgeLocations = "locations"
// EdgeItems holds the string denoting the items edge name in mutations.
EdgeItems = "items"
// EdgeLabels holds the string denoting the labels edge name in mutations.
EdgeLabels = "labels"
// EdgeDocuments holds the string denoting the documents edge name in mutations.
EdgeDocuments = "documents"
// EdgeInvitationTokens holds the string denoting the invitation_tokens edge name in mutations.
EdgeInvitationTokens = "invitation_tokens"
// Table holds the table name of the group in the database.
Table = "groups"
// UsersTable is the table that holds the users relation/edge.
UsersTable = "users"
// UsersInverseTable is the table name for the User entity.
// It exists in this package in order to avoid circular dependency with the "user" package.
UsersInverseTable = "users"
// UsersColumn is the table column denoting the users relation/edge.
UsersColumn = "group_users"
// LocationsTable is the table that holds the locations relation/edge.
LocationsTable = "locations"
// LocationsInverseTable is the table name for the Location entity.
// It exists in this package in order to avoid circular dependency with the "location" package.
LocationsInverseTable = "locations"
// LocationsColumn is the table column denoting the locations relation/edge.
LocationsColumn = "group_locations"
// ItemsTable is the table that holds the items relation/edge.
ItemsTable = "items"
// ItemsInverseTable is the table name for the Item entity.
// It exists in this package in order to avoid circular dependency with the "item" package.
ItemsInverseTable = "items"
// ItemsColumn is the table column denoting the items relation/edge.
ItemsColumn = "group_items"
// LabelsTable is the table that holds the labels relation/edge.
LabelsTable = "labels"
// LabelsInverseTable is the table name for the Label entity.
// It exists in this package in order to avoid circular dependency with the "label" package.
LabelsInverseTable = "labels"
// LabelsColumn is the table column denoting the labels relation/edge.
LabelsColumn = "group_labels"
// DocumentsTable is the table that holds the documents relation/edge.
DocumentsTable = "documents"
// DocumentsInverseTable is the table name for the Document entity.
// It exists in this package in order to avoid circular dependency with the "document" package.
DocumentsInverseTable = "documents"
// DocumentsColumn is the table column denoting the documents relation/edge.
DocumentsColumn = "group_documents"
// InvitationTokensTable is the table that holds the invitation_tokens relation/edge.
InvitationTokensTable = "group_invitation_tokens"
// InvitationTokensInverseTable is the table name for the GroupInvitationToken entity.
// It exists in this package in order to avoid circular dependency with the "groupinvitationtoken" package.
InvitationTokensInverseTable = "group_invitation_tokens"
// InvitationTokensColumn is the table column denoting the invitation_tokens relation/edge.
InvitationTokensColumn = "group_invitation_tokens"
)
// Columns holds all SQL columns for group fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldName,
FieldCurrency,
}
// 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
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// 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 (
CurrencyUsd Currency = "usd"
)
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 CurrencyUsd:
return nil
default:
return fmt.Errorf("group: invalid enum value for currency field: %q", c)
}
}

View file

@ -1,196 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package item
import (
"time"
"github.com/google/uuid"
)
const (
// Label holds the string label denoting the item type in the database.
Label = "item"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldDescription holds the string denoting the description field in the database.
FieldDescription = "description"
// FieldImportRef holds the string denoting the import_ref field in the database.
FieldImportRef = "import_ref"
// FieldNotes holds the string denoting the notes field in the database.
FieldNotes = "notes"
// FieldQuantity holds the string denoting the quantity field in the database.
FieldQuantity = "quantity"
// FieldInsured holds the string denoting the insured field in the database.
FieldInsured = "insured"
// FieldSerialNumber holds the string denoting the serial_number field in the database.
FieldSerialNumber = "serial_number"
// FieldModelNumber holds the string denoting the model_number field in the database.
FieldModelNumber = "model_number"
// FieldManufacturer holds the string denoting the manufacturer field in the database.
FieldManufacturer = "manufacturer"
// FieldLifetimeWarranty holds the string denoting the lifetime_warranty field in the database.
FieldLifetimeWarranty = "lifetime_warranty"
// FieldWarrantyExpires holds the string denoting the warranty_expires field in the database.
FieldWarrantyExpires = "warranty_expires"
// FieldWarrantyDetails holds the string denoting the warranty_details field in the database.
FieldWarrantyDetails = "warranty_details"
// FieldPurchaseTime holds the string denoting the purchase_time field in the database.
FieldPurchaseTime = "purchase_time"
// FieldPurchaseFrom holds the string denoting the purchase_from field in the database.
FieldPurchaseFrom = "purchase_from"
// FieldPurchasePrice holds the string denoting the purchase_price field in the database.
FieldPurchasePrice = "purchase_price"
// FieldSoldTime holds the string denoting the sold_time field in the database.
FieldSoldTime = "sold_time"
// FieldSoldTo holds the string denoting the sold_to field in the database.
FieldSoldTo = "sold_to"
// FieldSoldPrice holds the string denoting the sold_price field in the database.
FieldSoldPrice = "sold_price"
// FieldSoldNotes holds the string denoting the sold_notes field in the database.
FieldSoldNotes = "sold_notes"
// EdgeGroup holds the string denoting the group edge name in mutations.
EdgeGroup = "group"
// EdgeLabel holds the string denoting the label edge name in mutations.
EdgeLabel = "label"
// EdgeLocation holds the string denoting the location edge name in mutations.
EdgeLocation = "location"
// EdgeFields holds the string denoting the fields edge name in mutations.
EdgeFields = "fields"
// EdgeAttachments holds the string denoting the attachments edge name in mutations.
EdgeAttachments = "attachments"
// Table holds the table name of the item in the database.
Table = "items"
// GroupTable is the table that holds the group relation/edge.
GroupTable = "items"
// GroupInverseTable is the table name for the Group entity.
// It exists in this package in order to avoid circular dependency with the "group" package.
GroupInverseTable = "groups"
// GroupColumn is the table column denoting the group relation/edge.
GroupColumn = "group_items"
// LabelTable is the table that holds the label relation/edge. The primary key declared below.
LabelTable = "label_items"
// LabelInverseTable is the table name for the Label entity.
// It exists in this package in order to avoid circular dependency with the "label" package.
LabelInverseTable = "labels"
// LocationTable is the table that holds the location relation/edge.
LocationTable = "items"
// LocationInverseTable is the table name for the Location entity.
// It exists in this package in order to avoid circular dependency with the "location" package.
LocationInverseTable = "locations"
// LocationColumn is the table column denoting the location relation/edge.
LocationColumn = "location_items"
// FieldsTable is the table that holds the fields relation/edge.
FieldsTable = "item_fields"
// FieldsInverseTable is the table name for the ItemField entity.
// It exists in this package in order to avoid circular dependency with the "itemfield" package.
FieldsInverseTable = "item_fields"
// FieldsColumn is the table column denoting the fields relation/edge.
FieldsColumn = "item_fields"
// AttachmentsTable is the table that holds the attachments relation/edge.
AttachmentsTable = "attachments"
// AttachmentsInverseTable is the table name for the Attachment entity.
// It exists in this package in order to avoid circular dependency with the "attachment" package.
AttachmentsInverseTable = "attachments"
// AttachmentsColumn is the table column denoting the attachments relation/edge.
AttachmentsColumn = "item_attachments"
)
// Columns holds all SQL columns for item fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldName,
FieldDescription,
FieldImportRef,
FieldNotes,
FieldQuantity,
FieldInsured,
FieldSerialNumber,
FieldModelNumber,
FieldManufacturer,
FieldLifetimeWarranty,
FieldWarrantyExpires,
FieldWarrantyDetails,
FieldPurchaseTime,
FieldPurchaseFrom,
FieldPurchasePrice,
FieldSoldTime,
FieldSoldTo,
FieldSoldPrice,
FieldSoldNotes,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "items"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
"group_items",
"location_items",
}
var (
// LabelPrimaryKey and LabelColumn2 are the table columns denoting the
// primary key for the label relation (M2M).
LabelPrimaryKey = []string{"label_id", "item_id"}
)
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
for i := range ForeignKeys {
if column == ForeignKeys[i] {
return true
}
}
return false
}
var (
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
DescriptionValidator func(string) error
// ImportRefValidator is a validator for the "import_ref" field. It is called by the builders before save.
ImportRefValidator func(string) error
// NotesValidator is a validator for the "notes" field. It is called by the builders before save.
NotesValidator func(string) error
// DefaultQuantity holds the default value on creation for the "quantity" field.
DefaultQuantity int
// DefaultInsured holds the default value on creation for the "insured" field.
DefaultInsured bool
// SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save.
SerialNumberValidator func(string) error
// ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save.
ModelNumberValidator func(string) error
// ManufacturerValidator is a validator for the "manufacturer" field. It is called by the builders before save.
ManufacturerValidator func(string) error
// DefaultLifetimeWarranty holds the default value on creation for the "lifetime_warranty" field.
DefaultLifetimeWarranty bool
// WarrantyDetailsValidator is a validator for the "warranty_details" field. It is called by the builders before save.
WarrantyDetailsValidator func(string) error
// DefaultPurchasePrice holds the default value on creation for the "purchase_price" field.
DefaultPurchasePrice float64
// DefaultSoldPrice holds the default value on creation for the "sold_price" field.
DefaultSoldPrice float64
// SoldNotesValidator is a validator for the "sold_notes" field. It is called by the builders before save.
SoldNotesValidator func(string) error
// DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID
)

View file

@ -1,89 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package location
import (
"time"
"github.com/google/uuid"
)
const (
// Label holds the string label denoting the location type in the database.
Label = "location"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldDescription holds the string denoting the description field in the database.
FieldDescription = "description"
// EdgeGroup holds the string denoting the group edge name in mutations.
EdgeGroup = "group"
// EdgeItems holds the string denoting the items edge name in mutations.
EdgeItems = "items"
// Table holds the table name of the location in the database.
Table = "locations"
// GroupTable is the table that holds the group relation/edge.
GroupTable = "locations"
// GroupInverseTable is the table name for the Group entity.
// It exists in this package in order to avoid circular dependency with the "group" package.
GroupInverseTable = "groups"
// GroupColumn is the table column denoting the group relation/edge.
GroupColumn = "group_locations"
// ItemsTable is the table that holds the items relation/edge.
ItemsTable = "items"
// ItemsInverseTable is the table name for the Item entity.
// It exists in this package in order to avoid circular dependency with the "item" package.
ItemsInverseTable = "items"
// ItemsColumn is the table column denoting the items relation/edge.
ItemsColumn = "location_items"
)
// Columns holds all SQL columns for location fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldName,
FieldDescription,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "locations"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
"group_locations",
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
for i := range ForeignKeys {
if column == ForeignKeys[i] {
return true
}
}
return false
}
var (
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
DescriptionValidator func(string) error
// DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID
)

View file

@ -1,50 +0,0 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"github.com/hay-kot/homebox/backend/ent/schema/mixins"
)
// DocumentToken holds the schema definition for the DocumentToken entity.
type DocumentToken struct {
ent.Schema
}
func (DocumentToken) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.BaseMixin{},
}
}
// Fields of the DocumentToken.
func (DocumentToken) Fields() []ent.Field {
return []ent.Field{
field.Bytes("token").
NotEmpty().
Unique(),
field.Int("uses").
Default(1),
field.Time("expires_at").
Default(func() time.Time { return time.Now().Add(time.Minute * 10) }),
}
}
// Edges of the DocumentToken.
func (DocumentToken) Edges() []ent.Edge {
return []ent.Edge{
edge.From("document", Document.Type).
Ref("document_tokens").
Unique(),
}
}
func (DocumentToken) Indexes() []ent.Index {
return []ent.Index{
index.Fields("token"),
}
}

View file

@ -1,62 +0,0 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/schema/mixins"
)
// Group holds the schema definition for the Group entity.
type Group struct {
ent.Schema
}
func (Group) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.BaseMixin{},
}
}
// Fields of the Home.
func (Group) Fields() []ent.Field {
return []ent.Field{
field.String("name").
MaxLen(255).
NotEmpty(),
field.Enum("currency").
Default("usd").
Values("usd"), // TODO: add more currencies
}
}
// Edges of the Home.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
edge.To("locations", Location.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
edge.To("items", Item.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
edge.To("labels", Label.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
edge.To("documents", Document.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
edge.To("invitation_tokens", GroupInvitationToken.Type).
Annotations(entsql.Annotation{
OnDelete: entsql.Cascade,
}),
}
}

View file

@ -1,46 +1,77 @@
module github.com/hay-kot/homebox/backend
go 1.19
go 1.23.0
toolchain go1.24.2
require (
ariga.io/atlas v0.7.2-0.20220927111110-867ee0cca56a
entgo.io/ent v0.11.3
github.com/ardanlabs/conf/v2 v2.2.0
github.com/go-chi/chi/v5 v5.0.7
github.com/google/uuid v1.3.0
github.com/mattn/go-sqlite3 v1.14.15
github.com/rs/zerolog v1.28.0
github.com/stretchr/testify v1.8.0
github.com/swaggo/http-swagger v1.3.3
github.com/swaggo/swag v1.8.6
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
ariga.io/atlas v0.32.0
entgo.io/ent v0.14.4
github.com/ardanlabs/conf/v3 v3.7.1
github.com/containrrr/shoutrrr v0.8.0
github.com/go-chi/chi/v5 v5.2.1
github.com/go-playground/validator/v10 v10.26.0
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/google/uuid v1.6.0
github.com/gorilla/schema v1.4.1
github.com/hay-kot/httpkit v0.0.11
github.com/mattn/go-sqlite3 v1.14.27
github.com/olahol/melody v1.2.1
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0
github.com/swaggo/http-swagger/v2 v2.0.2
github.com/swaggo/swag v1.16.4
github.com/yeqown/go-qrcode/v2 v2.2.5
github.com/yeqown/go-qrcode/writer/standard v1.2.5
golang.org/x/crypto v0.37.0
modernc.org/sqlite v1.37.0
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.7 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/hashicorp/hcl/v2 v2.14.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-openapi/inflect v0.21.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kr/pretty v0.3.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.16 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/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/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
github.com/zclconf/go-cty v1.11.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220923203811-8be639271d50 // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/swaggo/files/v2 v2.0.2 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/image v0.26.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.62.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.9.1 // indirect
)

View file

@ -1,125 +1,190 @@
ariga.io/atlas v0.7.2-0.20220927111110-867ee0cca56a h1:6/nt4DODfgxzHTTg3tYy7YkVzruGQGZ/kRvXpA45KUo=
ariga.io/atlas v0.7.2-0.20220927111110-867ee0cca56a/go.mod h1:ft47uSh5hWGDCmQC9DsztZg6Xk+KagM5Ts/mZYKb9JE=
entgo.io/ent v0.11.3 h1:F5FBGAWiDCGder7YT+lqMnyzXl6d0xU3xMBM/SO3CMc=
entgo.io/ent v0.11.3/go.mod h1:mvDhvynOzAsOe7anH7ynPPtMjA/eeXP96kAfweevyxc=
ariga.io/atlas v0.32.0 h1:y+77nueMrExLiKlz1CcPKh/nU7VSlWfBbwCShsJyvCw=
ariga.io/atlas v0.32.0/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=
entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
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=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/ardanlabs/conf/v2 v2.2.0 h1:ar1+TYIYAh2Tdeg2DQroh7ruR56/vJR8BDfzDIrXgtk=
github.com/ardanlabs/conf/v2 v2.2.0/go.mod h1:m37ZKdW9jwMUEhGX36jRNt8VzSQ/HVmSziLZH2p33nY=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/ardanlabs/conf/v3 v3.7.1 h1:GIV7ylesF/0NexhnJdLmzsi2NIVYY2wVhR0UfvpmAeQ=
github.com/ardanlabs/conf/v3 v3.7.1/go.mod h1:IIucqD+601gt3jfhMXVukxoT16LnoGVd2DzRC2GhHiA=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/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=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
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=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
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.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-logr/logr v1.2.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.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-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.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
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/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl/v2 v2.14.1 h1:x0BpjfZ+CYdbiz+8yZTQ+gdLO7IXvOut7Da+XJayx34=
github.com/hashicorp/hcl/v2 v2.14.1/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0=
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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
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.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hay-kot/httpkit v0.0.11 h1:ZdB2uqsFBSDpfUoClGK5c5orjBjQkEVSXh7fZX5FKEk=
github.com/hay-kot/httpkit v0.0.11/go.mod h1:0kZdk5/swzdfqfg2c6pBWimcgeJ9PTyO97EbHnYl2Sw=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/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=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
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.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
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.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
github.com/mattn/go-sqlite3 v1.14.27/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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/olahol/melody v1.2.1 h1:xdwRkzHxf+B0w4TKbGpUSSkV516ZucQZJIWLztOWICQ=
github.com/olahol/melody v1.2.1/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=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc=
github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo=
github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg=
github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0=
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
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.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/yeqown/go-qrcode/v2 v2.2.5 h1:HCOe2bSjkhZyYoyyNaXNzh4DJZll6inVJQQw+8228Zk=
github.com/yeqown/go-qrcode/v2 v2.2.5/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw=
github.com/yeqown/go-qrcode/writer/standard v1.2.5 h1:m+5BUIcbsaG2md76FIqI/oZULrAju8tsk47eOohovQ0=
github.com/yeqown/go-qrcode/writer/standard v1.2.5/go.mod h1:O4MbzsotGCvy8upYPCR91j81dr5XLT7heuljcNXW+oQ=
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/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa h1:uKcci2q7Qtp6nMTC/AAvfNUAldFtJuHWV9/5QWiypts=
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
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=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View file

@ -1,69 +0,0 @@
package config
import (
"encoding/json"
"errors"
"fmt"
"github.com/ardanlabs/conf/v2"
"os"
)
const (
ModeDevelopment = "development"
ModeProduction = "production"
)
type Config struct {
Mode string `yaml:"mode" conf:"default:development"` // development or production
Web WebConfig `yaml:"web"`
Storage Storage `yaml:"storage"`
Log LoggerConf `yaml:"logger"`
Mailer MailerConf `yaml:"mailer"`
Swagger SwaggerConf `yaml:"swagger"`
Demo bool `yaml:"demo"`
}
type SwaggerConf struct {
Host string `yaml:"host" conf:"default:localhost:7745"`
Scheme string `yaml:"scheme" conf:"default:http"`
}
type WebConfig struct {
Port string `yaml:"port" conf:"default:7745"`
Host string `yaml:"host"`
MaxUploadSize int64 `yaml:"max_file_upload" conf:"default:10"`
}
// New parses the CLI/Config file and returns a Config struct. If the file argument is an empty string, the
// file is not read. If the file is not empty, the file is read and the Config struct is returned.
func New() (*Config, error) {
var cfg Config
const prefix = "HBOX"
help, err := conf.Parse(prefix, &cfg)
if err != nil {
if errors.Is(err, conf.ErrHelpWanted) {
fmt.Println(help)
os.Exit(0)
}
return &cfg, fmt.Errorf("parsing config: %w", err)
}
return &cfg, nil
}
// Print prints the configuration to stdout as a json indented string
// This is useful for debugging. If the marshaller errors out, it will panic.
func (c *Config) Print() {
res, err := json.MarshalIndent(c, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(res))
}

View file

@ -1,11 +0,0 @@
package config
const (
DriverSqlite3 = "sqlite3"
)
type Storage struct {
// Data is the path to the root directory
Data string `yaml:"data" conf:"default:./.data"`
SqliteUrl string `yaml:"sqlite-url" conf:"default:./.data/homebox.db?_fk=1"`
}

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

@ -0,0 +1,67 @@
// 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"
)
type AllServices struct {
User *UserService
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) {
return func(o *options) {
o.autoIncrementAssetID = v
}
}
func WithCurrencies(v []currencies.Currency) func(*options) {
return func(o *options) {
o.currencies = v
}
}
func New(repos *repo.AllRepos, 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 {
opt(options)
}
return &AllServices{
User: &UserService{repos},
Group: &GroupService{repos},
Items: &ItemService{
repo: repos,
autoIncrementAssetID: options.autoIncrementAssetID,
},
BackgroundService: &BackgroundService{repos},
Currencies: currencies.NewCurrencyService(options.currencies),
}
}

View file

@ -4,7 +4,7 @@ import (
"context"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/data/repo"
)
type contextKeys struct {

View file

@ -5,7 +5,7 @@ import (
"testing"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert"
)

View file

@ -3,19 +3,20 @@ package services
import (
"context"
"log"
"math/rand"
"os"
"testing"
"time"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/repo"
"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/mattn/go-sqlite3"
)
var (
fk = faker.NewFaker()
tbus = eventbus.New()
tCtx = Context{}
tClient *ent.Client
@ -49,8 +50,6 @@ func bootstrap() {
}
func TestMain(m *testing.M) {
rand.Seed(int64(time.Now().Unix()))
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
@ -62,9 +61,14 @@ func TestMain(m *testing.M) {
}
tClient = client
tRepos = repo.New(tClient, os.TempDir()+"/homebox")
tSvc = NewServices(tRepos)
defer client.Close()
tRepos = repo.New(tClient, tbus, os.TempDir()+"/homebox")
defaults, _ := currencies.CollectionCurrencies(
currencies.CollectDefaults(),
)
tSvc = New(tRepos, WithCurrencies(defaults))
defer func() { _ = client.Close() }()
bootstrap()
tCtx = Context{

View file

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

View file

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

View file

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

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