Compare commits

...

19 Commits

Author SHA1 Message Date
Hayden
8a71a51c43 conditionally filter parent locations 2022-11-02 11:29:17 -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
244 changed files with 4933 additions and 1887 deletions

View File

@@ -16,6 +16,8 @@ jobs:
- name: Install Task - name: Install Task
uses: arduino/setup-task@v1 uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
@@ -28,7 +30,7 @@ jobs:
args: --timeout=6m args: --timeout=6m
- name: Build API - name: Build API
run: task api:build run: task go:build
- name: Test - name: Test
run: task api:coverage run: task go:coverage

View File

@@ -14,6 +14,8 @@ jobs:
- name: Install Task - name: Install Task
uses: arduino/setup-task@v1 uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2

View File

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

2
.gitignore vendored
View File

@@ -3,7 +3,6 @@ backend/.data/*
config.yml config.yml
homebox.db homebox.db
.idea .idea
.DS_Store .DS_Store
test-mailer.json test-mailer.json
node_modules node_modules
@@ -32,6 +31,7 @@ node_modules
go.work go.work
.task/ .task/
backend/.env backend/.env
build/*
# Output Directory for Nuxt/Frontend during build step # Output Directory for Nuxt/Frontend during build step
backend/app/api/public/* backend/app/api/public/*

View File

@@ -10,5 +10,8 @@
"package.json": "package-lock.json, yarn.lock, .eslintrc.js, tsconfig.json, .prettierrc, .editorconfig, pnpm-lock.yaml, postcss.config.js, tailwind.config.js", "package.json": "package-lock.json, yarn.lock, .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", "docker-compose.yml": "Dockerfile, .dockerignore, docker-compose.dev.yml, docker-compose.yml",
"README.md": "LICENSE, SECURITY.md" "README.md": "LICENSE, SECURITY.md"
} },
"cSpell.words": [
"debughandlers"
]
} }

51
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,51 @@
# 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 API's, 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 can 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 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.

View File

@@ -21,7 +21,7 @@ WORKDIR /go/src/app
COPY ./backend . COPY ./backend .
RUN go get -d -v ./... RUN go get -d -v ./...
RUN rm -rf ./app/api/public RUN rm -rf ./app/api/public
COPY --from=frontend-builder /app/.output/public ./app/api/public COPY --from=frontend-builder /app/.output/public ./app/api/static/public
RUN CGO_ENABLED=1 GOOS=linux go build \ RUN CGO_ENABLED=1 GOOS=linux go build \
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \ -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-o /go/bin/api \ -o /go/bin/api \

View File

@@ -5,11 +5,12 @@ env:
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure" UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
tasks: tasks:
setup: setup:
desc: Install dependencies desc: Install development dependencies
cmds: cmds:
- go install github.com/swaggo/swag/cmd/swag@latest - go install github.com/swaggo/swag/cmd/swag@latest
- cd backend && go mod tidy - cd backend && go mod tidy
- cd frontend && pnpm install --shamefully-hoist - cd frontend && pnpm install --shamefully-hoist
generate: generate:
desc: | desc: |
Generates collateral files from the backend project Generates collateral files from the backend project
@@ -17,27 +18,27 @@ tasks:
deps: deps:
- db:generate - db:generate
cmds: cmds:
- cd backend/app/api/ && swag fmt - cd backend/app/api/static && swag fmt --dir=../
- cd backend/app/api/ && swag init --dir=./,../../internal,../../pkgs - cd backend/app/api/static && swag init --dir=../,../../../internal,../../../pkgs
- | - |
npx swagger-typescript-api \ npx swagger-typescript-api \
--no-client \ --no-client \
--modular \ --modular \
--path ./backend/app/api/docs/swagger.json \ --path ./backend/app/api/static/docs/swagger.json \
--output ./frontend/lib/api/types --output ./frontend/lib/api/types
- python3 ./scripts/process-types.py ./frontend/lib/api/types/data-contracts.ts - python3 ./scripts/process-types.py ./frontend/lib/api/types/data-contracts.ts
sources: sources:
- "./backend/app/api/**/*" - "./backend/app/api/**/*"
- "./backend/internal/repo/**/*" - "./backend/internal/data/**"
- "./backend/internal/services/**/*" - "./backend/internal/services/**/*"
- "./scripts/process-types.py" - "./scripts/process-types.py"
generates: generates:
- "./frontend/lib/api/types/data-contracts.ts" - "./frontend/lib/api/types/data-contracts.ts"
- "./backend/ent/schema" - "./backend/internal/data/ent/schema"
- "./backend/app/api/docs/swagger.json" - "./backend/app/api/static/docs/swagger.json"
- "./backend/app/api/docs/swagger.yaml" - "./backend/app/api/static/docs/swagger.yaml"
api: go:run:
desc: Starts the backend api server (depends on generate task) desc: Starts the backend api server (depends on generate task)
deps: deps:
- generate - generate
@@ -45,57 +46,72 @@ tasks:
- cd backend && go run ./app/api/ {{ .CLI_ARGS }} - cd backend && go run ./app/api/ {{ .CLI_ARGS }}
silent: false silent: false
api:build: go:test:
desc: Runs all go tests using gotestsum - supports passing gotestsum args
cmds: cmds:
- cd backend && go build ./app/api/ - cd backend && gotestsum {{ .CLI_ARGS }} ./...
silent: true
api:test: go:coverage:
cmds: desc: Runs all go tests with -race flag and generates a coverage report
- cd backend && go test ./app/api/
silent: true
api:watch:
cmds:
- cd backend && gotestsum --watch ./...
api:coverage:
cmds: cmds:
- cd backend && go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover - cd backend && go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
silent: true silent: true
test:ci: go:tidy:
desc: Runs go mod tidy on the backend
cmds: cmds:
- cd backend && go build ./app/api - cd backend && go mod tidy
- backend/api &
- sleep 5
- cd frontend && pnpm run test:ci
silent: true
frontend:watch: go:lint:
desc: Starts the vitest test runner in watch mode desc: Runs golangci-lint
cmds: cmds:
- cd frontend && pnpm vitest --watch - cd backend && golangci-lint run ./...
frontend: go:all:
desc: Run frontend development server desc: Runs all go test and lint related tasks
cmds: cmds:
- cd frontend && pnpm dev - task: go:tidy
- task: go:lint
- task: go:test
go:build:
desc: Builds the backend binary
cmds:
- cd backend && go build -o ../build/backend ./app/api
db:generate: db:generate:
desc: Run Entgo.io Code Generation desc: Run Entgo.io Code Generation
cmds: cmds:
- | - |
cd backend && go generate ./... \ cd backend/internal/ && go generate ./... \
--template=ent/schema/templates/has_id.tmpl --template=./data/ent/schema/templates/has_id.tmpl
sources: sources:
- "./backend/ent/schema/**/*" - "./backend/internal/data/ent/schema/**/*"
generates: generates:
- "./backend/ent/" - "./backend/internal/ent/"
db:migration: db:migration:
desc: Runs the database diff engine to generate a SQL migration files desc: Runs the database diff engine to generate a SQL migration files
deps: deps:
- db:generate - db:generate
cmds: cmds:
- cd backend && go run app/migrations/main.go {{ .CLI_ARGS }} - cd backend && go run app/tools/migrations/main.go {{ .CLI_ARGS }}
ui:watch:
desc: Starts the vitest test runner in watch mode
cmds:
- cd frontend && pnpm run test:watch
ui:dev:
desc: Run frontend development server
cmds:
- cd frontend && pnpm dev
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 &
- sleep 5
- cd frontend && pnpm run test:ci
silent: true

View File

@@ -3,10 +3,10 @@ package main
import ( import (
"time" "time"
"github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/config" "github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/pkgs/mailer" "github.com/hay-kot/homebox/backend/pkgs/mailer"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
) )

View File

@@ -5,7 +5,7 @@ import (
"encoding/csv" "encoding/csv"
"strings" "strings"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )

View File

@@ -0,0 +1,16 @@
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())
}

View File

@@ -3,7 +3,8 @@ package v1
import ( import (
"net/http" "net/http"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
) )
@@ -26,6 +27,7 @@ func WithRegistration(allowRegistration bool) func(*V1Controller) {
} }
type V1Controller struct { type V1Controller struct {
repo *repo.AllRepos
svc *services.AllServices svc *services.AllServices
maxUploadSize int64 maxUploadSize int64
isDemo bool isDemo bool
@@ -57,8 +59,9 @@ func BaseUrlFunc(prefix string) func(s string) string {
} }
} }
func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller { func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, options ...func(*V1Controller)) *V1Controller {
ctrl := &V1Controller{ ctrl := &V1Controller{
repo: repos,
svc: svc, svc: svc,
allowRegistration: true, allowRegistration: true,
} }
@@ -76,9 +79,9 @@ func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller))
// @Produce json // @Produce json
// @Success 200 {object} ApiSummary // @Success 200 {object} ApiSummary
// @Router /v1/status [GET] // @Router /v1/status [GET]
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc { func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
server.Respond(w, http.StatusOK, ApiSummary{ return server.Respond(w, http.StatusOK, ApiSummary{
Healthy: ready(), Healthy: ready(),
Title: "Go API Template", Title: "Go API Template",
Message: "Welcome to the Go API Template Application!", Message: "Welcome to the Go API Template Application!",

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.NewInvalidRouteKeyError(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

@@ -5,7 +5,8 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -32,17 +33,15 @@ type (
// @Produce json // @Produce json
// @Success 200 {object} TokenResponse // @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST] // @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc { func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
loginForm := &LoginForm{} loginForm := &LoginForm{}
switch r.Header.Get("Content-Type") { switch r.Header.Get("Content-Type") {
case server.ContentFormUrlEncoded: case server.ContentFormUrlEncoded:
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
server.Respond(w, http.StatusBadRequest, server.Wrap(err)) return server.Respond(w, http.StatusBadRequest, server.Wrap(err))
log.Error().Err(err).Msg("failed to parse form")
return
} }
loginForm.Username = r.PostFormValue("username") loginForm.Username = r.PostFormValue("username")
@@ -52,27 +51,31 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
if err != nil { if err != nil {
log.Err(err).Msg("failed to decode login form") log.Err(err).Msg("failed to decode login form")
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
return
} }
default: default:
server.Respond(w, http.StatusBadRequest, errors.New("invalid content type")) return server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
return
} }
if loginForm.Username == "" || loginForm.Password == "" { if loginForm.Username == "" || loginForm.Password == "" {
server.RespondError(w, http.StatusBadRequest, errors.New("username and password are required")) return validate.NewFieldErrors(
return validate.FieldError{
Field: "username",
Error: "username or password is empty",
},
validate.FieldError{
Field: "password",
Error: "username or password is empty",
},
)
} }
newToken, err := ctrl.svc.User.Login(r.Context(), loginForm.Username, loginForm.Password) newToken, err := ctrl.svc.User.Login(r.Context(), loginForm.Username, loginForm.Password)
if err != nil { if err != nil {
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, TokenResponse{ return server.Respond(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw, Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt, ExpiresAt: newToken.ExpiresAt,
}) })
@@ -85,23 +88,19 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/users/logout [POST] // @Router /v1/users/logout [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc { func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context()) token := services.UseTokenCtx(r.Context())
if token == "" { if token == "" {
server.RespondError(w, http.StatusUnauthorized, errors.New("no token within request context")) return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
return
} }
err := ctrl.svc.User.Logout(r.Context(), token) err := ctrl.svc.User.Logout(r.Context(), token)
if err != nil { if err != nil {
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }
@@ -113,22 +112,18 @@ func (ctrl *V1Controller) HandleAuthLogout() http.HandlerFunc {
// @Success 200 // @Success 200
// @Router /v1/users/refresh [GET] // @Router /v1/users/refresh [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() http.HandlerFunc { func (ctrl *V1Controller) HandleAuthRefresh() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
requestToken := services.UseTokenCtx(r.Context()) requestToken := services.UseTokenCtx(r.Context())
if requestToken == "" { if requestToken == "" {
server.RespondError(w, http.StatusUnauthorized, errors.New("no user token found")) return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
return
} }
newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken) newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken)
if err != nil { if err != nil {
server.RespondUnauthorized(w) return validate.NewUnauthorizedError()
return
} }
server.Respond(w, http.StatusOK, newToken) return server.Respond(w, http.StatusOK, newToken)
} }
} }

View File

@@ -2,11 +2,11 @@ package v1
import ( import (
"net/http" "net/http"
"strings"
"time" "time"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -24,6 +24,26 @@ type (
} }
) )
// HandleGroupGet godoc
// @Summary Get the current user's group
// @Tags Group
// @Produce json
// @Success 200 {object} repo.GroupStatistics
// @Router /v1/groups/statistics [Get]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
stats, err := ctrl.repo.Groups.GroupStatistics(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
}
}
// HandleGroupGet godoc // HandleGroupGet godoc
// @Summary Get the current user's group // @Summary Get the current user's group
// @Tags Group // @Tags Group
@@ -31,7 +51,7 @@ type (
// @Success 200 {object} repo.Group // @Success 200 {object} repo.Group
// @Router /v1/groups [Get] // @Router /v1/groups [Get]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc { func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc {
return ctrl.handleGroupGeneral() return ctrl.handleGroupGeneral()
} }
@@ -43,42 +63,40 @@ func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc {
// @Success 200 {object} repo.Group // @Success 200 {object} repo.Group
// @Router /v1/groups [Put] // @Router /v1/groups [Put]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc {
return ctrl.handleGroupGeneral() return ctrl.handleGroupGeneral()
} }
func (ctrl *V1Controller) handleGroupGeneral() http.HandlerFunc { func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
group, err := ctrl.svc.Group.Get(ctx) group, err := ctrl.repo.Groups.GroupByID(ctx, ctx.GID)
if err != nil { if err != nil {
log.Err(err).Msg("failed to get group") log.Err(err).Msg("failed to get group")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower caseÍ return server.Respond(w, http.StatusOK, group)
server.Respond(w, http.StatusOK, group)
case http.MethodPut: case http.MethodPut:
data := repo.GroupUpdate{} data := repo.GroupUpdate{}
if err := server.Decode(r, &data); err != nil { if err := server.Decode(r, &data); err != nil {
server.RespondError(w, http.StatusBadRequest, err) return validate.NewRequestError(err, http.StatusBadRequest)
return
} }
group, err := ctrl.svc.Group.UpdateGroup(ctx, data) group, err := ctrl.svc.Group.UpdateGroup(ctx, data)
if err != nil { if err != nil {
log.Err(err).Msg("failed to update group") log.Err(err).Msg("failed to update group")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower case
server.Respond(w, http.StatusOK, group) return server.Respond(w, http.StatusOK, group)
} }
return nil
} }
} }
@@ -90,13 +108,12 @@ func (ctrl *V1Controller) handleGroupGeneral() http.HandlerFunc {
// @Success 200 {object} GroupInvitation // @Success 200 {object} GroupInvitation
// @Router /v1/groups/invitations [Post] // @Router /v1/groups/invitations [Post]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc { func (ctrl *V1Controller) HandleGroupInvitationsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
data := GroupInvitationCreate{} data := GroupInvitationCreate{}
if err := server.Decode(r, &data); err != nil { if err := server.Decode(r, &data); err != nil {
log.Err(err).Msg("failed to decode user registration data") log.Err(err).Msg("failed to decode user registration data")
server.RespondError(w, http.StatusBadRequest, err) return validate.NewRequestError(err, http.StatusBadRequest)
return
} }
if data.ExpiresAt.IsZero() { if data.ExpiresAt.IsZero() {
@@ -108,11 +125,10 @@ func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt) token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt)
if err != nil { if err != nil {
log.Err(err).Msg("failed to create new token") log.Err(err).Msg("failed to create new token")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusCreated, GroupInvitation{ return server.Respond(w, http.StatusCreated, GroupInvitation{
Token: token, Token: token,
ExpiresAt: data.ExpiresAt, ExpiresAt: data.ExpiresAt,
Uses: data.Uses, Uses: data.Uses,

View File

@@ -3,12 +3,10 @@ package v1
import ( import (
"encoding/csv" "encoding/csv"
"net/http" "net/http"
"net/url"
"strconv"
"github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -25,48 +23,29 @@ import (
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{} // @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET] // @Router /v1/items [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc { func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc {
uuidList := func(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
}
intOrNegativeOne := func(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
return -1
}
return i
}
extractQuery := func(r *http.Request) repo.ItemQuery { extractQuery := func(r *http.Request) repo.ItemQuery {
params := r.URL.Query() params := r.URL.Query()
return repo.ItemQuery{ return repo.ItemQuery{
Page: intOrNegativeOne(params.Get("page")), Page: queryIntOrNegativeOne(params.Get("page")),
PageSize: intOrNegativeOne(params.Get("perPage")), PageSize: queryIntOrNegativeOne(params.Get("perPage")),
Search: params.Get("q"), Search: params.Get("q"),
LocationIDs: uuidList(params, "locations"), LocationIDs: queryUUIDList(params, "locations"),
LabelIDs: uuidList(params, "labels"), LabelIDs: queryUUIDList(params, "labels"),
IncludeArchived: queryBool(params.Get("includeArchived")),
} }
} }
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
items, err := ctrl.svc.Items.Query(ctx, extractQuery(r)) items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r))
if err != nil { if err != nil {
log.Err(err).Msg("failed to get items") log.Err(err).Msg("failed to get items")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, items) return server.Respond(w, http.StatusOK, items)
} }
} }
@@ -78,24 +57,22 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
// @Success 200 {object} repo.ItemSummary // @Success 200 {object} repo.ItemSummary
// @Router /v1/items [POST] // @Router /v1/items [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc { func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
createData := repo.ItemCreate{} createData := repo.ItemCreate{}
if err := server.Decode(r, &createData); err != nil { if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode request body") log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
user := services.UseUserCtx(r.Context()) user := services.UseUserCtx(r.Context())
item, err := ctrl.svc.Items.Create(r.Context(), user.GroupID, createData) item, err := ctrl.repo.Items.Create(r.Context(), user.GroupID, createData)
if err != nil { if err != nil {
log.Err(err).Msg("failed to create item") log.Err(err).Msg("failed to create item")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusCreated, item) return server.Respond(w, http.StatusCreated, item)
} }
} }
@@ -107,7 +84,7 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
// @Success 200 {object} repo.ItemOut // @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [GET] // @Router /v1/items/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc { func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc {
return ctrl.handleItemsGeneral() return ctrl.handleItemsGeneral()
} }
@@ -119,7 +96,7 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/items/{id} [DELETE] // @Router /v1/items/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc { func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc {
return ctrl.handleItemsGeneral() return ctrl.handleItemsGeneral()
} }
@@ -132,54 +109,49 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
// @Success 200 {object} repo.ItemOut // @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [PUT] // @Router /v1/items/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleItemUpdate() server.HandlerFunc {
return ctrl.handleItemsGeneral() return ctrl.handleItemsGeneral()
} }
func (ctrl *V1Controller) handleItemsGeneral() http.HandlerFunc { func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(w, r) ID, err := ctrl.routeID(r)
if err != nil { if err != nil {
return return err
} }
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
items, err := ctrl.svc.Items.GetOne(r.Context(), ctx.GID, ID) items, err := ctrl.repo.Items.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil { if err != nil {
log.Err(err).Msg("failed to get item") log.Err(err).Msg("failed to get item")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, items) return server.Respond(w, http.StatusOK, items)
return
case http.MethodDelete: case http.MethodDelete:
err = ctrl.svc.Items.Delete(r.Context(), ctx.GID, ID) err = ctrl.repo.Items.DeleteByGroup(r.Context(), ctx.GID, ID)
if err != nil { if err != nil {
log.Err(err).Msg("failed to delete item") log.Err(err).Msg("failed to delete item")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
return
case http.MethodPut: case http.MethodPut:
body := repo.ItemUpdate{} body := repo.ItemUpdate{}
if err := server.Decode(r, &body); err != nil { if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode request body") log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
body.ID = ID body.ID = ID
result, err := ctrl.svc.Items.Update(r.Context(), ctx.GID, body) result, err := ctrl.repo.Items.UpdateByGroup(r.Context(), ctx.GID, body)
if err != nil { if err != nil {
log.Err(err).Msg("failed to update item") log.Err(err).Msg("failed to update item")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, result) return server.Respond(w, http.StatusOK, result)
} }
return nil
} }
} }
@@ -191,29 +163,26 @@ func (ctrl *V1Controller) handleItemsGeneral() http.HandlerFunc {
// @Param csv formData file true "Image to upload" // @Param csv formData file true "Image to upload"
// @Router /v1/items/import [Post] // @Router /v1/items/import [Post]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc { func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20) err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil { if err != nil {
log.Err(err).Msg("failed to parse multipart form") log.Err(err).Msg("failed to parse multipart form")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
file, _, err := r.FormFile("csv") file, _, err := r.FormFile("csv")
if err != nil { if err != nil {
log.Err(err).Msg("failed to get file from form") log.Err(err).Msg("failed to get file from form")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
reader := csv.NewReader(file) reader := csv.NewReader(file)
data, err := reader.ReadAll() data, err := reader.ReadAll()
if err != nil { if err != nil {
log.Err(err).Msg("failed to read csv") log.Err(err).Msg("failed to read csv")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
user := services.UseUserCtx(r.Context()) user := services.UseUserCtx(r.Context())
@@ -221,10 +190,9 @@ func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, data) _, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, data)
if err != nil { if err != nil {
log.Err(err).Msg("failed to import items") log.Err(err).Msg("failed to import items")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }

View File

@@ -5,11 +5,10 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/go-chi/chi/v5" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -29,19 +28,19 @@ type (
// @Param type formData string true "Type of file" // @Param type formData string true "Type of file"
// @Param name formData string true "name of the file including extension" // @Param name formData string true "name of the file including extension"
// @Success 200 {object} repo.ItemOut // @Success 200 {object} repo.ItemOut
// @Failure 422 {object} []server.ValidationError // @Failure 422 {object} server.ErrorResponse
// @Router /v1/items/{id}/attachments [POST] // @Router /v1/items/{id}/attachments [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
err := r.ParseMultipartForm(ctrl.maxUploadSize << 20) err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
if err != nil { if err != nil {
log.Err(err).Msg("failed to parse multipart form") log.Err(err).Msg("failed to parse multipart form")
server.RespondError(w, http.StatusBadRequest, errors.New("failed to parse multipart form")) return validate.NewRequestError(errors.New("failed to parse multipart form"), http.StatusBadRequest)
return
} }
errs := make(server.ValidationErrors, 0) errs := validate.NewFieldErrors()
file, _, err := r.FormFile("file") file, _, err := r.FormFile("file")
if err != nil { if err != nil {
@@ -51,8 +50,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
errs = errs.Append("file", "file is required") errs = errs.Append("file", "file is required")
default: default:
log.Err(err).Msg("failed to get file from form") log.Err(err).Msg("failed to get file from form")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
} }
@@ -62,9 +60,8 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
errs = errs.Append("name", "name is required") errs = errs.Append("name", "name is required")
} }
if errs.HasErrors() { if !errs.Nil() {
server.Respond(w, http.StatusUnprocessableEntity, errs) return server.Respond(w, http.StatusUnprocessableEntity, errs)
return
} }
attachmentType := r.FormValue("type") attachmentType := r.FormValue("type")
@@ -72,9 +69,9 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
attachmentType = attachment.TypeAttachment.String() attachmentType = attachment.TypeAttachment.String()
} }
id, err := ctrl.routeID(w, r) id, err := ctrl.routeID(r)
if err != nil { if err != nil {
return return err
} }
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
@@ -89,11 +86,10 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
if err != nil { if err != nil {
log.Err(err).Msg("failed to add attachment") log.Err(err).Msg("failed to add attachment")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusCreated, item) return server.Respond(w, http.StatusCreated, item)
} }
} }
@@ -106,21 +102,21 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
// @Success 200 // @Success 200
// @Router /v1/items/{id}/attachments/download [GET] // @Router /v1/items/{id}/attachments/download [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentDownload() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
token := server.GetParam(r, "token", "") token := server.GetParam(r, "token", "")
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token) doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
if err != nil { if err != nil {
log.Err(err).Msg("failed to get attachment") log.Err(err).Msg("failed to get attachment")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title)) w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title))
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(w, r, doc.Path) http.ServeFile(w, r, doc.Path)
return nil
} }
} }
@@ -133,7 +129,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
// @Success 200 {object} ItemAttachmentToken // @Success 200 {object} ItemAttachmentToken
// @Router /v1/items/{id}/attachments/{attachment_id} [GET] // @Router /v1/items/{id}/attachments/{attachment_id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentToken() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler return ctrl.handleItemAttachmentsHandler
} }
@@ -145,7 +141,7 @@ func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/items/{id}/attachments/{attachment_id} [DELETE] // @Router /v1/items/{id}/attachments/{attachment_id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentDelete() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler return ctrl.handleItemAttachmentsHandler
} }
@@ -158,66 +154,60 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
// @Success 200 {object} repo.ItemOut // @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT] // @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentUpdate() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler return ctrl.handleItemAttachmentsHandler
} }
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) { func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) error {
ID, err := ctrl.routeID(w, r) ID, err := ctrl.routeID(r)
if err != nil { if err != nil {
return return err
} }
attachmentId, err := uuid.Parse(chi.URLParam(r, "attachment_id")) attachmentID, err := ctrl.routeUUID(r, "attachment_id")
if err != nil { if err != nil {
log.Err(err).Msg("failed to parse attachment_id param") return err
server.RespondError(w, http.StatusBadRequest, err)
return
} }
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
switch r.Method { switch r.Method {
// Token Handler // Token Handler
case http.MethodGet: case http.MethodGet:
token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentId) token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentID)
if err != nil { if err != nil {
switch err { switch err {
case services.ErrNotFound: case services.ErrNotFound:
log.Err(err). log.Err(err).
Str("id", attachmentId.String()). Str("id", attachmentID.String()).
Msg("failed to find attachment with id") Msg("failed to find attachment with id")
server.RespondError(w, http.StatusNotFound, err) return validate.NewRequestError(err, http.StatusNotFound)
case services.ErrFileNotFound: case services.ErrFileNotFound:
log.Err(err). log.Err(err).
Str("id", attachmentId.String()). Str("id", attachmentID.String()).
Msg("failed to find file path for attachment with id") Msg("failed to find file path for attachment with id")
log.Warn().Msg("attachment with no file path removed from database") log.Warn().Msg("attachment with no file path removed from database")
server.RespondError(w, http.StatusNotFound, err) return validate.NewRequestError(err, http.StatusNotFound)
default: default:
log.Err(err).Msg("failed to get attachment") log.Err(err).Msg("failed to get attachment")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
} }
server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token}) return server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
// Delete Attachment Handler // Delete Attachment Handler
case http.MethodDelete: case http.MethodDelete:
err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentId) err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentID)
if err != nil { if err != nil {
log.Err(err).Msg("failed to delete attachment") log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
// Update Attachment Handler // Update Attachment Handler
case http.MethodPut: case http.MethodPut:
@@ -225,18 +215,18 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
err = server.Decode(r, &attachment) err = server.Decode(r, &attachment)
if err != nil { if err != nil {
log.Err(err).Msg("failed to decode attachment") log.Err(err).Msg("failed to decode attachment")
server.RespondError(w, http.StatusBadRequest, err) return validate.NewRequestError(err, http.StatusBadRequest)
return
} }
attachment.ID = attachmentId attachment.ID = attachmentID
val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ID, &attachment) val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ID, &attachment)
if err != nil { if err != nil {
log.Err(err).Msg("failed to delete attachment") log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, val) return server.Respond(w, http.StatusOK, val)
} }
return nil
} }

View File

@@ -3,9 +3,10 @@ package v1
import ( import (
"net/http" "net/http"
"github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -17,16 +18,15 @@ import (
// @Success 200 {object} server.Results{items=[]repo.LabelOut} // @Success 200 {object} server.Results{items=[]repo.LabelOut}
// @Router /v1/labels [GET] // @Router /v1/labels [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
user := services.UseUserCtx(r.Context()) user := services.UseUserCtx(r.Context())
labels, err := ctrl.svc.Labels.GetAll(r.Context(), user.GroupID) labels, err := ctrl.repo.Labels.GetAll(r.Context(), user.GroupID)
if err != nil { if err != nil {
log.Err(err).Msg("error getting labels") log.Err(err).Msg("error getting labels")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, server.Results{Items: labels}) return server.Respond(w, http.StatusOK, server.Results{Items: labels})
} }
} }
@@ -38,24 +38,22 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
// @Success 200 {object} repo.LabelSummary // @Success 200 {object} repo.LabelSummary
// @Router /v1/labels [POST] // @Router /v1/labels [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
createData := repo.LabelCreate{} createData := repo.LabelCreate{}
if err := server.Decode(r, &createData); err != nil { if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("error decoding label create data") log.Err(err).Msg("error decoding label create data")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
user := services.UseUserCtx(r.Context()) user := services.UseUserCtx(r.Context())
label, err := ctrl.svc.Labels.Create(r.Context(), user.GroupID, createData) label, err := ctrl.repo.Labels.Create(r.Context(), user.GroupID, createData)
if err != nil { if err != nil {
log.Err(err).Msg("error creating label") log.Err(err).Msg("error creating label")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusCreated, label) return server.Respond(w, http.StatusCreated, label)
} }
} }
@@ -67,7 +65,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/labels/{id} [DELETE] // @Router /v1/labels/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc {
return ctrl.handleLabelsGeneral() return ctrl.handleLabelsGeneral()
} }
@@ -79,7 +77,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
// @Success 200 {object} repo.LabelOut // @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [GET] // @Router /v1/labels/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc {
return ctrl.handleLabelsGeneral() return ctrl.handleLabelsGeneral()
} }
@@ -91,60 +89,55 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
// @Success 200 {object} repo.LabelOut // @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [PUT] // @Router /v1/labels/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc {
return ctrl.handleLabelsGeneral() return ctrl.handleLabelsGeneral()
} }
func (ctrl *V1Controller) handleLabelsGeneral() http.HandlerFunc { func (ctrl *V1Controller) handleLabelsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(w, r) ID, err := ctrl.routeID(r)
if err != nil { if err != nil {
return return err
} }
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
labels, err := ctrl.svc.Labels.Get(r.Context(), ctx.GID, ID) labels, err := ctrl.repo.Labels.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil { if err != nil {
if ent.IsNotFound(err) { if ent.IsNotFound(err) {
log.Err(err). log.Err(err).
Str("id", ID.String()). Str("id", ID.String()).
Msg("label not found") Msg("label not found")
server.RespondError(w, http.StatusNotFound, err) return validate.NewRequestError(err, http.StatusNotFound)
return
} }
log.Err(err).Msg("error getting label") log.Err(err).Msg("error getting label")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, labels) return server.Respond(w, http.StatusOK, labels)
case http.MethodDelete: case http.MethodDelete:
err = ctrl.svc.Labels.Delete(r.Context(), ctx.GID, ID) err = ctrl.repo.Labels.DeleteByGroup(ctx, ctx.GID, ID)
if err != nil { if err != nil {
log.Err(err).Msg("error deleting label") log.Err(err).Msg("error deleting label")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut: case http.MethodPut:
body := repo.LabelUpdate{} body := repo.LabelUpdate{}
if err := server.Decode(r, &body); err != nil { if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("error decoding label update data") return validate.NewRequestError(err, http.StatusInternalServerError)
server.RespondError(w, http.StatusInternalServerError, err)
return
} }
body.ID = ID body.ID = ID
result, err := ctrl.svc.Labels.Update(r.Context(), ctx.GID, body) result, err := ctrl.repo.Labels.UpdateByGroup(ctx, ctx.GID, body)
if err != nil { if err != nil {
log.Err(err).Msg("error updating label") return validate.NewRequestError(err, http.StatusInternalServerError)
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
} }
return server.Respond(w, http.StatusOK, result)
}
return nil
} }
} }

View File

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

View File

@@ -4,8 +4,9 @@ import (
"net/http" "net/http"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -17,29 +18,26 @@ import (
// @Param payload body services.UserRegistration true "User Data" // @Param payload body services.UserRegistration true "User Data"
// @Success 204 // @Success 204
// @Router /v1/users/register [Post] // @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc { func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
regData := services.UserRegistration{} regData := services.UserRegistration{}
if err := server.Decode(r, &regData); err != nil { if err := server.Decode(r, &regData); err != nil {
log.Err(err).Msg("failed to decode user registration data") log.Err(err).Msg("failed to decode user registration data")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
if !ctrl.allowRegistration && regData.GroupToken == "" { if !ctrl.allowRegistration && regData.GroupToken == "" {
server.RespondError(w, http.StatusForbidden, nil) return validate.NewRequestError(nil, http.StatusForbidden)
return
} }
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData) _, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
if err != nil { if err != nil {
log.Err(err).Msg("failed to register user") log.Err(err).Msg("failed to register user")
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }
@@ -50,17 +48,16 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
// @Success 200 {object} server.Result{item=repo.UserOut} // @Success 200 {object} server.Result{item=repo.UserOut}
// @Router /v1/users/self [GET] // @Router /v1/users/self [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc { func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context()) token := services.UseTokenCtx(r.Context())
usr, err := ctrl.svc.User.GetSelf(r.Context(), token) usr, err := ctrl.svc.User.GetSelf(r.Context(), token)
if usr.ID == uuid.Nil || err != nil { if usr.ID == uuid.Nil || err != nil {
log.Err(err).Msg("failed to get user") log.Err(err).Msg("failed to get user")
server.RespondServerError(w) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, server.Wrap(usr)) return server.Respond(w, http.StatusOK, server.Wrap(usr))
} }
} }
@@ -72,24 +69,22 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
// @Success 200 {object} server.Result{item=repo.UserUpdate} // @Success 200 {object} server.Result{item=repo.UserUpdate}
// @Router /v1/users/self [PUT] // @Router /v1/users/self [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
updateData := repo.UserUpdate{} updateData := repo.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil { if err := server.Decode(r, &updateData); err != nil {
log.Err(err).Msg("failed to decode user update data") log.Err(err).Msg("failed to decode user update data")
server.RespondError(w, http.StatusBadRequest, err) return validate.NewRequestError(err, http.StatusBadRequest)
return
} }
actor := services.UseUserCtx(r.Context()) actor := services.UseUserCtx(r.Context())
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData) newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
if err != nil { if err != nil {
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusOK, server.Wrap(newData)) return server.Respond(w, http.StatusOK, server.Wrap(newData))
} }
} }
@@ -100,20 +95,18 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/users/self [DELETE] // @Router /v1/users/self [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() http.HandlerFunc { func (ctrl *V1Controller) HandleUserSelfDelete() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo { if ctrl.isDemo {
server.RespondError(w, http.StatusForbidden, nil) return validate.NewRequestError(nil, http.StatusForbidden)
return
} }
actor := services.UseUserCtx(r.Context()) actor := services.UseUserCtx(r.Context())
if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil { if err := ctrl.svc.User.DeleteSelf(r.Context(), actor.ID); err != nil {
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }
@@ -131,11 +124,10 @@ type (
// @Param payload body ChangePassword true "Password Payload" // @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT] // @Router /v1/users/change-password [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleUserSelfChangePassword() http.HandlerFunc { func (ctrl *V1Controller) HandleUserSelfChangePassword() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo { if ctrl.isDemo {
server.RespondError(w, http.StatusForbidden, nil) return validate.NewRequestError(nil, http.StatusForbidden)
return
} }
var cp ChangePassword var cp ChangePassword
@@ -148,10 +140,9 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() http.HandlerFunc {
ok := ctrl.svc.User.ChangePassword(ctx, cp.Current, cp.New) ok := ctrl.svc.User.ChangePassword(ctx, cp.Current, cp.New)
if !ok { if !ok {
server.RespondError(w, http.StatusInternalServerError, err) return validate.NewRequestError(err, http.StatusInternalServerError)
return
} }
server.Respond(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }

View File

@@ -4,7 +4,7 @@ import (
"os" "os"
"strings" "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"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -15,7 +15,7 @@ func (a *app) setupLogger() {
// Logger Init // Logger Init
// zerolog.TimeFieldFormat = zerolog.TimeFormatUnix // zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
if a.conf.Log.Format != config.LogFormatJSON { 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)) log.Level(getLevel(a.conf.Log.Level))

View File

@@ -2,18 +2,20 @@ package main
import ( import (
"context" "context"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
atlas "ariga.io/atlas/sql/migrate" atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql/schema" "entgo.io/ent/dialect/sql/schema"
"github.com/hay-kot/homebox/backend/app/api/docs" "github.com/hay-kot/homebox/backend/app/api/static/docs"
"github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/config" "github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/migrations" "github.com/hay-kot/homebox/backend/internal/data/migrations"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/homebox/backend/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -113,17 +115,25 @@ func run(cfg *config.Config) error {
app.services = services.New(app.repos) app.services = services.New(app.repos)
// ========================================================================= // =========================================================================
// Start Server // Start Server\
logger := log.With().Caller().Logger()
mwLogger := mid.Logger(logger)
if app.conf.Mode == config.ModeDevelopment {
mwLogger = mid.SugarLogger(logger)
}
app.server = server.NewServer( app.server = server.NewServer(
server.WithHost(app.conf.Web.Host), server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port), server.WithPort(app.conf.Web.Port),
server.WithMiddleware(
mwLogger,
mid.Errors(logger),
mid.Panic(app.conf.Mode == config.ModeDevelopment),
),
) )
routes := app.newRouter(app.repos) app.mountRoutes(app.repos)
if app.conf.Mode != config.ModeDevelopment {
app.logRoutes(routes)
}
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port) log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
@@ -153,5 +163,14 @@ func run(cfg *config.Config) error {
app.SetupDemo() app.SetupDemo()
} }
return app.server.Start(routes) if cfg.Debug.Enabled {
debugrouter := app.debugRouter()
go func() {
if err := http.ListenAndServe(":"+cfg.Debug.Port, debugrouter); err != nil {
log.Fatal().Err(err).Msg("failed to start debug server")
}
}()
}
return app.server.Start()
} }

View File

@@ -1,143 +1,34 @@
package main package main
import ( import (
"fmt" "errors"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/go-chi/chi/v5" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/go-chi/chi/v5/middleware" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"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/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
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)
}
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))
}
// mwAuthToken is a middleware that will check the database for a stateful token // 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. // 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 { func (a *app) mwAuthToken(next server.Handler) server.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
requestToken := r.Header.Get("Authorization") requestToken := r.Header.Get("Authorization")
if requestToken == "" { if requestToken == "" {
server.RespondUnauthorized(w) return validate.NewRequestError(errors.New("Authorization header is required"), http.StatusUnauthorized)
return
} }
requestToken = strings.TrimPrefix(requestToken, "Bearer ") requestToken = strings.TrimPrefix(requestToken, "Bearer ")
usr, err := a.services.User.GetSelf(r.Context(), requestToken) usr, err := a.services.User.GetSelf(r.Context(), requestToken)
// Check the database for the token // Check the database for the token
if err != nil { if err != nil {
server.RespondUnauthorized(w) return validate.NewRequestError(errors.New("Authorization header is required"), http.StatusUnauthorized)
return
} }
r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken)) r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken))
return next.ServeHTTP(w, r)
next.ServeHTTP(w, r)
})
}
// 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("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)),
)
}) })
} }

View File

@@ -10,11 +10,11 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"github.com/go-chi/chi/v5" "github.com/hay-kot/homebox/backend/app/api/handlers/debughandlers"
_ "github.com/hay-kot/homebox/backend/app/api/docs" v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1"
v1 "github.com/hay-kot/homebox/backend/app/api/v1" _ "github.com/hay-kot/homebox/backend/app/api/static/docs"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/rs/zerolog/log" "github.com/hay-kot/homebox/backend/pkgs/server"
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
) )
@@ -23,106 +23,90 @@ const prefix = "/api"
var ( var (
ErrDir = errors.New("path is dir") ErrDir = errors.New("path is dir")
//go:embed all:public/* //go:embed all:static/public/*
public embed.FS 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 // registerRoutes registers all the routes for the API
func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux { func (a *app) mountRoutes(repos *repo.AllRepos) {
registerMimes() registerMimes()
r := chi.NewRouter() a.server.Get("/swagger/*", server.ToHandler(httpSwagger.Handler(
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(fmt.Sprintf("%s://%s/swagger/doc.json", a.conf.Swagger.Scheme, a.conf.Swagger.Host)),
)) )))
// ========================================================================= // =========================================================================
// API Version 1 // API Version 1
v1Base := v1.BaseUrlFunc(prefix) v1Base := v1.BaseUrlFunc(prefix)
v1Ctrl := v1.NewControllerV1(a.services,
v1Ctrl := v1.NewControllerV1(
a.services,
a.repos,
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize), v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
v1.WithRegistration(a.conf.AllowRegistration), v1.WithRegistration(a.conf.AllowRegistration),
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
) )
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
a.server.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: version, Version: version,
Commit: commit, Commit: commit,
BuildTime: buildTime, BuildTime: buildTime,
})) }))
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration()) a.server.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
r.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin()) a.server.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
// Attachment download URl needs a `token` query param to be passed in the request. // Attachment download URl needs a `token` query param to be passed in the request.
// and also needs to be outside of the `auth` middleware. // and also needs to be outside of the `auth` middleware.
r.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload()) a.server.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload())
r.Group(func(r chi.Router) { a.server.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf(), a.mwAuthToken)
r.Use(a.mwAuthToken) a.server.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate(), a.mwAuthToken)
r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf()) a.server.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete(), a.mwAuthToken)
r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate()) a.server.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout(), a.mwAuthToken)
r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete()) a.server.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh(), a.mwAuthToken)
r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout()) a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), a.mwAuthToken)
r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh())
r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword())
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate()) a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), a.mwAuthToken)
a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), a.mwAuthToken)
// TODO: I don't like /groups being the URL for users // TODO: I don't like /groups being the URL for users
r.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet()) a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), a.mwAuthToken)
r.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate()) a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), a.mwAuthToken)
r.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll()) a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), a.mwAuthToken)
r.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate()) a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), a.mwAuthToken)
r.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet()) a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), a.mwAuthToken)
r.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate()) a.server.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate(), a.mwAuthToken)
r.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete()) a.server.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete(), a.mwAuthToken)
r.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll()) a.server.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll(), a.mwAuthToken)
r.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate()) a.server.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate(), a.mwAuthToken)
r.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet()) a.server.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet(), a.mwAuthToken)
r.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate()) a.server.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate(), a.mwAuthToken)
r.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete()) a.server.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete(), a.mwAuthToken)
r.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll()) a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), a.mwAuthToken)
r.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport()) a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), a.mwAuthToken)
r.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate()) a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), a.mwAuthToken)
r.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet()) a.server.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet(), a.mwAuthToken)
r.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate()) a.server.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate(), a.mwAuthToken)
r.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete()) a.server.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete(), a.mwAuthToken)
r.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate()) a.server.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate(), a.mwAuthToken)
r.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken()) a.server.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken(), a.mwAuthToken)
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate()) a.server.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate(), a.mwAuthToken)
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete()) a.server.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete(), a.mwAuthToken)
})
r.NotFound(notFoundHandler()) a.server.NotFound(notFoundHandler())
return r
}
// 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
walkFunc := func(method string, route string, handler http.Handler, middleware ...func(http.Handler) http.Handler) error {
text := "[" + method + "]"
for len(text) < desiredSpaces {
text = text + " "
}
fmt.Printf("Registered Route: %s%s\n", text, route)
return nil
}
if err := chi.Walk(r, walkFunc); err != nil {
fmt.Printf("Logging err: %s\n", err.Error())
}
} }
func registerMimes() { func registerMimes() {
@@ -139,7 +123,7 @@ func registerMimes() {
// notFoundHandler perform the main logic around handling the internal SPA embed and ensuring that // notFoundHandler perform the main logic around handling the internal SPA embed and ensuring that
// the client side routing is handled correctly. // the client side routing is handled correctly.
func notFoundHandler() http.HandlerFunc { func notFoundHandler() server.HandlerFunc {
tryRead := func(fs embed.FS, prefix, requestedPath string, w http.ResponseWriter) error { tryRead := func(fs embed.FS, prefix, requestedPath string, w http.ResponseWriter) error {
f, err := fs.Open(path.Join(prefix, requestedPath)) f, err := fs.Open(path.Join(prefix, requestedPath))
if err != nil { if err != nil {
@@ -158,17 +142,16 @@ func notFoundHandler() http.HandlerFunc {
return err return err
} }
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) error {
err := tryRead(public, "public", r.URL.Path, w) err := tryRead(public, "static/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)
if err != nil { 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
}
} }

View File

@@ -113,6 +113,30 @@ const docTemplate = `{
} }
} }
}, },
"/v1/groups/statistics": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Get the current user's group",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.GroupStatistics"
}
}
}
}
},
"/v1/items": { "/v1/items": {
"get": { "get": {
"security": [ "security": [
@@ -395,10 +419,7 @@ const docTemplate = `{
"422": { "422": {
"description": "Unprocessable Entity", "description": "Unprocessable Entity",
"schema": { "schema": {
"type": "array", "$ref": "#/definitions/server.ErrorResponse"
"items": {
"$ref": "#/definitions/server.ValidationError"
}
} }
} }
} }
@@ -735,6 +756,14 @@ const docTemplate = `{
"Locations" "Locations"
], ],
"summary": "Get All Locations", "summary": "Get All Locations",
"parameters": [
{
"type": "boolean",
"description": "Filter locations with parents",
"name": "filterChildren",
"in": "query"
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -845,6 +874,15 @@ const docTemplate = `{
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "required": true
},
{
"description": "Location Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.LocationUpdate"
}
} }
], ],
"responses": { "responses": {
@@ -1172,6 +1210,23 @@ const docTemplate = `{
} }
} }
}, },
"repo.GroupStatistics": {
"type": "object",
"properties": {
"totalItems": {
"type": "integer"
},
"totalLabels": {
"type": "integer"
},
"totalLocations": {
"type": "integer"
},
"totalUsers": {
"type": "integer"
}
}
},
"repo.GroupUpdate": { "repo.GroupUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1232,6 +1287,10 @@ const docTemplate = `{
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
} }
} }
}, },
@@ -1264,12 +1323,21 @@ const docTemplate = `{
"repo.ItemOut": { "repo.ItemOut": {
"type": "object", "type": "object",
"properties": { "properties": {
"archived": {
"type": "boolean"
},
"attachments": { "attachments": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/repo.ItemAttachment" "$ref": "#/definitions/repo.ItemAttachment"
} }
}, },
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@@ -1277,7 +1345,6 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"fields": { "fields": {
"description": "Future",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/repo.ItemField" "$ref": "#/definitions/repo.ItemField"
@@ -1301,6 +1368,8 @@ const docTemplate = `{
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary" "$ref": "#/definitions/repo.LocationSummary"
}, },
"manufacturer": { "manufacturer": {
@@ -1316,6 +1385,11 @@ const docTemplate = `{
"description": "Extras", "description": "Extras",
"type": "string" "type": "string"
}, },
"parent": {
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.ItemSummary"
},
"purchaseFrom": { "purchaseFrom": {
"type": "string" "type": "string"
}, },
@@ -1361,6 +1435,9 @@ const docTemplate = `{
"repo.ItemSummary": { "repo.ItemSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
"archived": {
"type": "boolean"
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@@ -1381,6 +1458,8 @@ const docTemplate = `{
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary" "$ref": "#/definitions/repo.LocationSummary"
}, },
"name": { "name": {
@@ -1397,6 +1476,9 @@ const docTemplate = `{
"repo.ItemUpdate": { "repo.ItemUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
"archived": {
"type": "boolean"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@@ -1439,6 +1521,11 @@ const docTemplate = `{
"description": "Extras", "description": "Extras",
"type": "string" "type": "string"
}, },
"parentId": {
"type": "string",
"x-nullable": true,
"x-omitempty": true
},
"purchaseFrom": { "purchaseFrom": {
"type": "string" "type": "string"
}, },
@@ -1553,6 +1640,12 @@ const docTemplate = `{
"repo.LocationOut": { "repo.LocationOut": {
"type": "object", "type": "object",
"properties": { "properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LocationSummary"
}
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@@ -1571,6 +1664,9 @@ const docTemplate = `{
"name": { "name": {
"type": "string" "type": "string"
}, },
"parent": {
"$ref": "#/definitions/repo.LocationSummary"
},
"updatedAt": { "updatedAt": {
"type": "string" "type": "string"
} }
@@ -1619,6 +1715,24 @@ const docTemplate = `{
} }
} }
}, },
"repo.LocationUpdate": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
}
}
},
"repo.PaginationResult-repo_ItemSummary": { "repo.PaginationResult-repo_ItemSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1676,6 +1790,20 @@ const docTemplate = `{
} }
} }
}, },
"server.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"server.Result": { "server.Result": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1695,17 +1823,6 @@ const docTemplate = `{
"items": {} "items": {}
} }
}, },
"server.ValidationError": {
"type": "object",
"properties": {
"field": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"services.UserRegistration": { "services.UserRegistration": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -105,6 +105,30 @@
} }
} }
}, },
"/v1/groups/statistics": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Get the current user's group",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.GroupStatistics"
}
}
}
}
},
"/v1/items": { "/v1/items": {
"get": { "get": {
"security": [ "security": [
@@ -387,10 +411,7 @@
"422": { "422": {
"description": "Unprocessable Entity", "description": "Unprocessable Entity",
"schema": { "schema": {
"type": "array", "$ref": "#/definitions/server.ErrorResponse"
"items": {
"$ref": "#/definitions/server.ValidationError"
}
} }
} }
} }
@@ -727,6 +748,14 @@
"Locations" "Locations"
], ],
"summary": "Get All Locations", "summary": "Get All Locations",
"parameters": [
{
"type": "boolean",
"description": "Filter locations with parents",
"name": "filterChildren",
"in": "query"
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@@ -837,6 +866,15 @@
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "required": true
},
{
"description": "Location Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.LocationUpdate"
}
} }
], ],
"responses": { "responses": {
@@ -1164,6 +1202,23 @@
} }
} }
}, },
"repo.GroupStatistics": {
"type": "object",
"properties": {
"totalItems": {
"type": "integer"
},
"totalLabels": {
"type": "integer"
},
"totalLocations": {
"type": "integer"
},
"totalUsers": {
"type": "integer"
}
}
},
"repo.GroupUpdate": { "repo.GroupUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1224,6 +1279,10 @@
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
} }
} }
}, },
@@ -1256,12 +1315,21 @@
"repo.ItemOut": { "repo.ItemOut": {
"type": "object", "type": "object",
"properties": { "properties": {
"archived": {
"type": "boolean"
},
"attachments": { "attachments": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/repo.ItemAttachment" "$ref": "#/definitions/repo.ItemAttachment"
} }
}, },
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@@ -1269,7 +1337,6 @@
"type": "string" "type": "string"
}, },
"fields": { "fields": {
"description": "Future",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/repo.ItemField" "$ref": "#/definitions/repo.ItemField"
@@ -1293,6 +1360,8 @@
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary" "$ref": "#/definitions/repo.LocationSummary"
}, },
"manufacturer": { "manufacturer": {
@@ -1308,6 +1377,11 @@
"description": "Extras", "description": "Extras",
"type": "string" "type": "string"
}, },
"parent": {
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.ItemSummary"
},
"purchaseFrom": { "purchaseFrom": {
"type": "string" "type": "string"
}, },
@@ -1353,6 +1427,9 @@
"repo.ItemSummary": { "repo.ItemSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
"archived": {
"type": "boolean"
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@@ -1373,6 +1450,8 @@
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary" "$ref": "#/definitions/repo.LocationSummary"
}, },
"name": { "name": {
@@ -1389,6 +1468,9 @@
"repo.ItemUpdate": { "repo.ItemUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
"archived": {
"type": "boolean"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@@ -1431,6 +1513,11 @@
"description": "Extras", "description": "Extras",
"type": "string" "type": "string"
}, },
"parentId": {
"type": "string",
"x-nullable": true,
"x-omitempty": true
},
"purchaseFrom": { "purchaseFrom": {
"type": "string" "type": "string"
}, },
@@ -1545,6 +1632,12 @@
"repo.LocationOut": { "repo.LocationOut": {
"type": "object", "type": "object",
"properties": { "properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LocationSummary"
}
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@@ -1563,6 +1656,9 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"parent": {
"$ref": "#/definitions/repo.LocationSummary"
},
"updatedAt": { "updatedAt": {
"type": "string" "type": "string"
} }
@@ -1611,6 +1707,24 @@
} }
} }
}, },
"repo.LocationUpdate": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
}
}
},
"repo.PaginationResult-repo_ItemSummary": { "repo.PaginationResult-repo_ItemSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1668,6 +1782,20 @@
} }
} }
}, },
"server.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"server.Result": { "server.Result": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1687,17 +1815,6 @@
"items": {} "items": {}
} }
}, },
"server.ValidationError": {
"type": "object",
"properties": {
"field": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"services.UserRegistration": { "services.UserRegistration": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -22,6 +22,17 @@ definitions:
updatedAt: updatedAt:
type: string type: string
type: object type: object
repo.GroupStatistics:
properties:
totalItems:
type: integer
totalLabels:
type: integer
totalLocations:
type: integer
totalUsers:
type: integer
type: object
repo.GroupUpdate: repo.GroupUpdate:
properties: properties:
currency: currency:
@@ -62,6 +73,9 @@ definitions:
type: string type: string
name: name:
type: string type: string
parentId:
type: string
x-nullable: true
type: object type: object
repo.ItemField: repo.ItemField:
properties: properties:
@@ -82,16 +96,21 @@ definitions:
type: object type: object
repo.ItemOut: repo.ItemOut:
properties: properties:
archived:
type: boolean
attachments: attachments:
items: items:
$ref: '#/definitions/repo.ItemAttachment' $ref: '#/definitions/repo.ItemAttachment'
type: array type: array
children:
items:
$ref: '#/definitions/repo.ItemSummary'
type: array
createdAt: createdAt:
type: string type: string
description: description:
type: string type: string
fields: fields:
description: Future
items: items:
$ref: '#/definitions/repo.ItemField' $ref: '#/definitions/repo.ItemField'
type: array type: array
@@ -109,6 +128,8 @@ definitions:
location: location:
$ref: '#/definitions/repo.LocationSummary' $ref: '#/definitions/repo.LocationSummary'
description: Edges description: Edges
x-nullable: true
x-omitempty: true
manufacturer: manufacturer:
type: string type: string
modelNumber: modelNumber:
@@ -118,6 +139,10 @@ definitions:
notes: notes:
description: Extras description: Extras
type: string type: string
parent:
$ref: '#/definitions/repo.ItemSummary'
x-nullable: true
x-omitempty: true
purchaseFrom: purchaseFrom:
type: string type: string
purchasePrice: purchasePrice:
@@ -149,6 +174,8 @@ definitions:
type: object type: object
repo.ItemSummary: repo.ItemSummary:
properties: properties:
archived:
type: boolean
createdAt: createdAt:
type: string type: string
description: description:
@@ -164,6 +191,8 @@ definitions:
location: location:
$ref: '#/definitions/repo.LocationSummary' $ref: '#/definitions/repo.LocationSummary'
description: Edges description: Edges
x-nullable: true
x-omitempty: true
name: name:
type: string type: string
quantity: quantity:
@@ -173,6 +202,8 @@ definitions:
type: object type: object
repo.ItemUpdate: repo.ItemUpdate:
properties: properties:
archived:
type: boolean
description: description:
type: string type: string
fields: fields:
@@ -202,6 +233,10 @@ definitions:
notes: notes:
description: Extras description: Extras
type: string type: string
parentId:
type: string
x-nullable: true
x-omitempty: true
purchaseFrom: purchaseFrom:
type: string type: string
purchasePrice: purchasePrice:
@@ -278,6 +313,10 @@ definitions:
type: object type: object
repo.LocationOut: repo.LocationOut:
properties: properties:
children:
items:
$ref: '#/definitions/repo.LocationSummary'
type: array
createdAt: createdAt:
type: string type: string
description: description:
@@ -290,6 +329,8 @@ definitions:
type: array type: array
name: name:
type: string type: string
parent:
$ref: '#/definitions/repo.LocationSummary'
updatedAt: updatedAt:
type: string type: string
type: object type: object
@@ -321,6 +362,18 @@ definitions:
updatedAt: updatedAt:
type: string type: string
type: object type: object
repo.LocationUpdate:
properties:
description:
type: string
id:
type: string
name:
type: string
parentId:
type: string
x-nullable: true
type: object
repo.PaginationResult-repo_ItemSummary: repo.PaginationResult-repo_ItemSummary:
properties: properties:
items: items:
@@ -358,6 +411,15 @@ definitions:
name: name:
type: string type: string
type: object type: object
server.ErrorResponse:
properties:
error:
type: string
fields:
additionalProperties:
type: string
type: object
type: object
server.Result: server.Result:
properties: properties:
details: {} details: {}
@@ -371,13 +433,6 @@ definitions:
properties: properties:
items: {} items: {}
type: object type: object
server.ValidationError:
properties:
field:
type: string
reason:
type: string
type: object
services.UserRegistration: services.UserRegistration:
properties: properties:
email: email:
@@ -516,6 +571,20 @@ paths:
summary: Get the current user summary: Get the current user
tags: tags:
- Group - Group
/v1/groups/statistics:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.GroupStatistics'
security:
- Bearer: []
summary: Get the current user's group
tags:
- Group
/v1/items: /v1/items:
get: get:
parameters: parameters:
@@ -672,9 +741,7 @@ paths:
"422": "422":
description: Unprocessable Entity description: Unprocessable Entity
schema: schema:
items: $ref: '#/definitions/server.ErrorResponse'
$ref: '#/definitions/server.ValidationError'
type: array
security: security:
- Bearer: [] - Bearer: []
summary: imports items into the database summary: imports items into the database
@@ -893,6 +960,11 @@ paths:
- Labels - Labels
/v1/locations: /v1/locations:
get: get:
parameters:
- description: Filter locations with parents
in: query
name: filterChildren
type: boolean
produces: produces:
- application/json - application/json
responses: responses:
@@ -976,6 +1048,12 @@ paths:
name: id name: id
required: true required: true
type: string type: string
- description: Location Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.LocationUpdate'
produces: produces:
- application/json - application/json
responses: responses:

View File

@@ -1,21 +0,0 @@
package v1
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
func (ctrl *V1Controller) routeID(w http.ResponseWriter, r *http.Request) (uuid.UUID, error) {
ID, 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, err
}
return ID, nil
}

View File

@@ -1,153 +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 ctrl.handleLocationGeneral()
}
// HandleLocationGet godocs
// @Summary Gets a location and fields
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
return ctrl.handleLocationGeneral()
}
// 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 ctrl.handleLocationGeneral()
}
func (ctrl *V1Controller) handleLocationGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(w, r)
if err != nil {
return
}
switch r.Method {
case http.MethodGet:
location, err := ctrl.svc.Location.GetOne(r.Context(), ctx.GID, ID)
if err != nil {
l := log.Err(err).
Str("ID", ID.String()).
Str("GID", ctx.GID.String())
if ent.IsNotFound(err) {
l.Msg("location not found")
server.RespondError(w, http.StatusNotFound, err)
return
}
l.Msg("failed to get location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, location)
case http.MethodPut:
body := repo.LocationUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode location update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
body.ID = ID
result, err := ctrl.svc.Location.Update(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
case http.MethodDelete:
err = ctrl.svc.Location.Delete(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
}

View File

@@ -5,7 +5,7 @@ import (
"log" "log"
"os" "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" atlas "ariga.io/atlas/sql/migrate"
_ "ariga.io/atlas/sql/sqlite" _ "ariga.io/atlas/sql/sqlite"
@@ -17,7 +17,7 @@ import (
func main() { func main() {
ctx := context.Background() ctx := context.Background()
// Create a local migration directory able to understand Atlas migration file format for replay. // 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 { if err != nil {
log.Fatalf("failed creating atlas migration directory: %v", err) log.Fatalf("failed creating atlas migration directory: %v", err)
} }

View File

@@ -7,10 +7,11 @@ require (
entgo.io/ent v0.11.3 entgo.io/ent v0.11.3
github.com/ardanlabs/conf/v2 v2.2.0 github.com/ardanlabs/conf/v2 v2.2.0
github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/chi/v5 v5.0.7
github.com/go-playground/validator/v10 v10.11.1
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/mattn/go-sqlite3 v1.14.15 github.com/mattn/go-sqlite3 v1.14.16
github.com/rs/zerolog v1.28.0 github.com/rs/zerolog v1.28.0
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.1
github.com/swaggo/http-swagger v1.3.3 github.com/swaggo/http-swagger v1.3.3
github.com/swaggo/swag v1.8.7 github.com/swaggo/swag v1.8.7
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
@@ -26,10 +27,12 @@ require (
github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.7 // indirect github.com/go-openapi/spec v0.20.7 // indirect
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/hashicorp/hcl/v2 v2.14.1 // indirect github.com/hashicorp/hcl/v2 v2.14.1 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect

View File

@@ -31,6 +31,14 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 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 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -43,6 +51,7 @@ github.com/hashicorp/hcl/v2 v2.14.1/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0z
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 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/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -50,6 +59,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 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.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.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@@ -62,17 +73,19 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 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/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 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 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
@@ -81,11 +94,14 @@ github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.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/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY= 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/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 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc=
@@ -94,16 +110,20 @@ github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0=
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= 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/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 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/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 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
@@ -119,11 +139,13 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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-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/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,24 @@
package services
import "github.com/hay-kot/homebox/backend/internal/data/repo"
type AllServices struct {
User *UserService
Group *GroupService
Items *ItemService
}
func New(repos *repo.AllRepos) *AllServices {
if repos == nil {
panic("repos cannot be nil")
}
return &AllServices{
User: &UserService{repos},
Group: &GroupService{repos},
Items: &ItemService{
repo: repos,
at: attachmentTokens{},
},
}
}

View File

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

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/google/uuid" "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" "github.com/stretchr/testify/assert"
) )

View File

@@ -8,8 +8,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/faker" "github.com/hay-kot/homebox/backend/pkgs/faker"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )

View File

@@ -2,10 +2,9 @@ package services
import ( import (
"errors" "errors"
"strings"
"time" "time"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/hasher" "github.com/hay-kot/homebox/backend/pkgs/hasher"
) )
@@ -13,10 +12,6 @@ type GroupService struct {
repos *repo.AllRepos repos *repo.AllRepos
} }
func (svc *GroupService) Get(ctx Context) (repo.Group, error) {
return svc.repos.Groups.GroupByID(ctx.Context, ctx.GID)
}
func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.Group, error) { func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.Group, error) {
if data.Name == "" { if data.Name == "" {
data.Name = ctx.User.GroupName data.Name = ctx.User.GroupName
@@ -26,8 +21,6 @@ func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.G
return repo.Group{}, errors.New("currency cannot be empty") return repo.Group{}, errors.New("currency cannot be empty")
} }
data.Currency = strings.ToLower(data.Currency)
return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GID, data) return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GID, data)
} }

View File

@@ -5,7 +5,7 @@ import (
"errors" "errors"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -23,31 +23,7 @@ type ItemService struct {
at attachmentTokens at attachmentTokens
} }
func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (repo.ItemOut, error) { func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data [][]string) (int, error) {
return svc.repo.Items.GetOneByGroup(ctx, gid, id)
}
func (svc *ItemService) Query(ctx Context, q repo.ItemQuery) (repo.PaginationResult[repo.ItemSummary], error) {
return svc.repo.Items.QueryByGroup(ctx, ctx.GID, q)
}
func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]repo.ItemSummary, error) {
return svc.repo.Items.GetAll(ctx, gid)
}
func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data repo.ItemCreate) (repo.ItemOut, error) {
return svc.repo.Items.Create(ctx, gid, data)
}
func (svc *ItemService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error {
return svc.repo.Items.DeleteByGroup(ctx, gid, id)
}
func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data repo.ItemUpdate) (repo.ItemOut, error) {
return svc.repo.Items.UpdateByGroup(ctx, gid, data)
}
func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]string) (int, error) {
loaded := []csvRow{} loaded := []csvRow{}
// Skip first row // Skip first row
@@ -90,7 +66,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
// Bootstrap the locations and labels so we can reuse the created IDs for the items // Bootstrap the locations and labels so we can reuse the created IDs for the items
locations := map[string]uuid.UUID{} locations := map[string]uuid.UUID{}
existingLocation, err := svc.repo.Locations.GetAll(ctx, gid) existingLocation, err := svc.repo.Locations.GetAll(ctx, GID, repo.LocationQuery{})
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -99,7 +75,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
} }
labels := map[string]uuid.UUID{} labels := map[string]uuid.UUID{}
existingLabels, err := svc.repo.Labels.GetAll(ctx, gid) existingLabels, err := svc.repo.Labels.GetAll(ctx, GID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -111,7 +87,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
// Locations // Locations
if _, exists := locations[row.Location]; !exists { if _, exists := locations[row.Location]; !exists {
result, err := svc.repo.Locations.Create(ctx, gid, repo.LocationCreate{ result, err := svc.repo.Locations.Create(ctx, GID, repo.LocationCreate{
Name: row.Location, Name: row.Location,
Description: "", Description: "",
}) })
@@ -127,7 +103,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
if _, exists := labels[label]; exists { if _, exists := labels[label]; exists {
continue continue
} }
result, err := svc.repo.Labels.Create(ctx, gid, repo.LabelCreate{ result, err := svc.repo.Labels.Create(ctx, GID, repo.LabelCreate{
Name: label, Name: label,
Description: "", Description: "",
}) })
@@ -143,7 +119,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
for _, row := range loaded { for _, row := range loaded {
// Check Import Ref // Check Import Ref
if row.Item.ImportRef != "" { if row.Item.ImportRef != "" {
exists, err := svc.repo.Items.CheckRef(ctx, gid, row.Item.ImportRef) exists, err := svc.repo.Items.CheckRef(ctx, GID, row.Item.ImportRef)
if exists { if exists {
continue continue
} }
@@ -163,7 +139,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
Str("location", row.Location). Str("location", row.Location).
Msgf("Creating Item: %s", row.Item.Name) Msgf("Creating Item: %s", row.Item.Name)
result, err := svc.repo.Items.Create(ctx, gid, repo.ItemCreate{ result, err := svc.repo.Items.Create(ctx, GID, repo.ItemCreate{
ImportRef: row.Item.ImportRef, ImportRef: row.Item.ImportRef,
Name: row.Item.Name, Name: row.Item.Name,
Description: row.Item.Description, Description: row.Item.Description,
@@ -176,7 +152,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
} }
// Update the item with the rest of the data // Update the item with the rest of the data
_, err = svc.repo.Items.UpdateByGroup(ctx, gid, repo.ItemUpdate{ _, err = svc.repo.Items.UpdateByGroup(ctx, GID, repo.ItemUpdate{
// Edges // Edges
LocationID: locationID, LocationID: locationID,
LabelIDs: labelIDs, LabelIDs: labelIDs,

View File

@@ -7,9 +7,9 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/hasher" "github.com/hay-kot/homebox/backend/pkgs/hasher"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -91,7 +91,7 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *re
return repo.ItemOut{}, err return repo.ItemOut{}, err
} }
return svc.GetOne(ctx, ctx.GID, itemId) return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
} }
// AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment // AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
@@ -118,7 +118,7 @@ func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename st
return repo.ItemOut{}, err return repo.ItemOut{}, err
} }
return svc.GetOne(ctx, ctx.GID, itemId) return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
} }
func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemId, attachmentId uuid.UUID) error { func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemId, attachmentId uuid.UUID) error {

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -19,7 +19,7 @@ func TestItemService_AddAttachment(t *testing.T) {
filepath: temp, filepath: temp,
} }
loc, err := tSvc.Location.Create(context.Background(), tGroup.ID, repo.LocationCreate{ loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, repo.LocationCreate{
Description: "test", Description: "test",
Name: "test", Name: "test",
}) })
@@ -32,7 +32,7 @@ func TestItemService_AddAttachment(t *testing.T) {
LocationID: loc.ID, LocationID: loc.ID,
} }
itm, err := svc.Create(context.Background(), tGroup.ID, itmC) itm, err := svc.repo.Items.Create(context.Background(), tGroup.ID, itmC)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, itm) assert.NotNil(t, itm)
t.Cleanup(func() { t.Cleanup(func() {

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
) )
var ErrInvalidCsv = errors.New("invalid csv") var ErrInvalidCsv = errors.New("invalid csv")

View File

@@ -3,18 +3,22 @@ package services
import ( import (
"bytes" "bytes"
"encoding/csv" "encoding/csv"
"fmt"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert"
) )
const CSV_DATA = ` const CSV_DATA = `
Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes
A,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,,,,,,, A,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,Description 1,TRUE,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,10/13/2021,,,,10/13/2021,
B,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,,,,,,, B,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,Description 2,FALSE,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,10/15/2021,,,,10/15/2021,
C,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,,,,,,, C,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,Description 3,TRUE,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,10/13/2021,,,,10/13/2021,
D,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,,,,,,, D,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,Description 4,FALSE,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,10/21/2020,,,,10/21/2020,
E,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,,,,,,, E,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,Description 5,TRUE,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,10/14/2020,,,,10/14/2020,
F,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,,,,,,,` F,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,Description 6,FALSE,,39351,Honeywell,,Amazon,65.98,09/30/2020,,09/30/2020,,,,09/30/2020,`
func loadcsv() [][]string { func loadcsv() [][]string {
reader := csv.NewReader(bytes.NewBuffer([]byte(CSV_DATA))) reader := csv.NewReader(bytes.NewBuffer([]byte(CSV_DATA)))
@@ -27,6 +31,33 @@ func loadcsv() [][]string {
return records return records
} }
func Test_CorrectDateParsing(t *testing.T) {
t.Parallel()
expected := []time.Time{
time.Date(2021, 10, 13, 0, 0, 0, 0, time.UTC),
time.Date(2021, 10, 15, 0, 0, 0, 0, time.UTC),
time.Date(2021, 10, 13, 0, 0, 0, 0, time.UTC),
time.Date(2020, 10, 21, 0, 0, 0, 0, time.UTC),
time.Date(2020, 10, 14, 0, 0, 0, 0, time.UTC),
time.Date(2020, 9, 30, 0, 0, 0, 0, time.UTC),
}
records := loadcsv()
for i, record := range records {
if i == 0 {
continue
}
entity := newCsvRow(record)
expected := expected[i-1]
assert.Equal(t, expected, entity.Item.PurchaseTime, fmt.Sprintf("Failed on row %d", i))
assert.Equal(t, expected, entity.Item.WarrantyExpires, fmt.Sprintf("Failed on row %d", i))
assert.Equal(t, expected, entity.Item.SoldTime, fmt.Sprintf("Failed on row %d", i))
}
}
func Test_csvRow_getLabels(t *testing.T) { func Test_csvRow_getLabels(t *testing.T) {
type fields struct { type fields struct {
LabelStr string LabelStr string

View File

@@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -22,7 +23,7 @@ func TestItemService_CsvImport(t *testing.T) {
assert.Equal(t, 0, count) assert.Equal(t, 0, count)
assert.NoError(t, err) assert.NoError(t, err)
items, err := svc.GetAll(context.Background(), tGroup.ID) items, err := svc.repo.Items.GetAll(context.Background(), tGroup.ID)
assert.NoError(t, err) assert.NoError(t, err)
t.Cleanup(func() { t.Cleanup(func() {
for _, item := range items { for _, item := range items {
@@ -38,22 +39,14 @@ func TestItemService_CsvImport(t *testing.T) {
dataCsv = append(dataCsv, newCsvRow(item)) dataCsv = append(dataCsv, newCsvRow(item))
} }
locationService := &LocationService{ allLocation, err := tRepos.Locations.GetAll(context.Background(), tGroup.ID, repo.LocationQuery{})
repos: tRepos,
}
LabelService := &LabelService{
repos: tRepos,
}
allLocation, err := locationService.GetAll(context.Background(), tGroup.ID)
assert.NoError(t, err) assert.NoError(t, err)
locNames := []string{} locNames := []string{}
for _, loc := range allLocation { for _, loc := range allLocation {
locNames = append(locNames, loc.Name) locNames = append(locNames, loc.Name)
} }
allLabels, err := LabelService.GetAll(context.Background(), tGroup.ID) allLabels, err := tRepos.Labels.GetAll(context.Background(), tGroup.ID)
assert.NoError(t, err) assert.NoError(t, err)
labelNames := []string{} labelNames := []string{}
for _, label := range allLabels { for _, label := range allLabels {

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/hasher" "github.com/hay-kot/homebox/backend/pkgs/hasher"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )

View File

@@ -1,7 +1,7 @@
package services package services
import ( import (
"github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
) )
func defaultLocations() []repo.LocationCreate { func defaultLocations() []repo.LocationCreate {

View File

@@ -9,9 +9,9 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
) )
// Attachment is the model entity for the Attachment schema. // Attachment is the model entity for the Attachment schema.

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.

View File

@@ -11,9 +11,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
) )
// AttachmentCreate is the builder for creating a Attachment entity. // AttachmentCreate is the builder for creating a Attachment entity.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// AttachmentDelete is the builder for deleting a Attachment entity. // AttachmentDelete is the builder for deleting a Attachment entity.

View File

@@ -11,10 +11,10 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// AttachmentQuery is the builder for querying Attachment entities. // AttachmentQuery is the builder for querying Attachment entities.

View File

@@ -12,10 +12,10 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// AttachmentUpdate is the builder for updating Attachment entities. // AttachmentUpdate is the builder for updating Attachment entities.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// AuthTokens is the model entity for the AuthTokens schema. // AuthTokens is the model entity for the AuthTokens schema.

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.

View File

@@ -11,8 +11,8 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// AuthTokensCreate is the builder for creating a AuthTokens entity. // AuthTokensCreate is the builder for creating a AuthTokens entity.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// AuthTokensDelete is the builder for deleting a AuthTokens entity. // AuthTokensDelete is the builder for deleting a AuthTokens entity.

View File

@@ -11,9 +11,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// AuthTokensQuery is the builder for querying AuthTokens entities. // AuthTokensQuery is the builder for querying AuthTokens entities.

View File

@@ -12,9 +12,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// AuthTokensUpdate is the builder for updating AuthTokens entities. // AuthTokensUpdate is the builder for updating AuthTokens entities.

View File

@@ -9,19 +9,19 @@ import (
"log" "log"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/migrate" "github.com/hay-kot/homebox/backend/internal/data/ent/migrate"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/itemfield" "github.com/hay-kot/homebox/backend/internal/data/ent/itemfield"
"github.com/hay-kot/homebox/backend/ent/label" "github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
"entgo.io/ent/dialect" "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
@@ -1043,6 +1043,38 @@ func (c *ItemClient) GetX(ctx context.Context, id uuid.UUID) *Item {
return obj return obj
} }
// QueryParent queries the parent edge of a Item.
func (c *ItemClient) QueryParent(i *Item) *ItemQuery {
query := &ItemQuery{config: c.config}
query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) {
id := i.ID
step := sqlgraph.NewStep(
sqlgraph.From(item.Table, item.FieldID, id),
sqlgraph.To(item.Table, item.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, item.ParentTable, item.ParentColumn),
)
fromV = sqlgraph.Neighbors(i.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryChildren queries the children edge of a Item.
func (c *ItemClient) QueryChildren(i *Item) *ItemQuery {
query := &ItemQuery{config: c.config}
query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) {
id := i.ID
step := sqlgraph.NewStep(
sqlgraph.From(item.Table, item.FieldID, id),
sqlgraph.To(item.Table, item.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, item.ChildrenTable, item.ChildrenColumn),
)
fromV = sqlgraph.Neighbors(i.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryGroup queries the group edge of a Item. // QueryGroup queries the group edge of a Item.
func (c *ItemClient) QueryGroup(i *Item) *GroupQuery { func (c *ItemClient) QueryGroup(i *Item) *GroupQuery {
query := &GroupQuery{config: c.config} query := &GroupQuery{config: c.config}
@@ -1441,6 +1473,38 @@ func (c *LocationClient) GetX(ctx context.Context, id uuid.UUID) *Location {
return obj return obj
} }
// QueryParent queries the parent edge of a Location.
func (c *LocationClient) QueryParent(l *Location) *LocationQuery {
query := &LocationQuery{config: c.config}
query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) {
id := l.ID
step := sqlgraph.NewStep(
sqlgraph.From(location.Table, location.FieldID, id),
sqlgraph.To(location.Table, location.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, location.ParentTable, location.ParentColumn),
)
fromV = sqlgraph.Neighbors(l.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryChildren queries the children edge of a Location.
func (c *LocationClient) QueryChildren(l *Location) *LocationQuery {
query := &LocationQuery{config: c.config}
query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) {
id := l.ID
step := sqlgraph.NewStep(
sqlgraph.From(location.Table, location.FieldID, id),
sqlgraph.To(location.Table, location.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, location.ChildrenTable, location.ChildrenColumn),
)
fromV = sqlgraph.Neighbors(l.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryGroup queries the group edge of a Location. // QueryGroup queries the group edge of a Location.
func (c *LocationClient) QueryGroup(l *Location) *GroupQuery { func (c *LocationClient) QueryGroup(l *Location) *GroupQuery {
query := &GroupQuery{config: c.config} query := &GroupQuery{config: c.config}

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
) )
// Document is the model entity for the Document schema. // Document is the model entity for the Document schema.

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.

View File

@@ -11,10 +11,10 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
) )
// DocumentCreate is the builder for creating a Document entity. // DocumentCreate is the builder for creating a Document entity.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// DocumentDelete is the builder for deleting a Document entity. // DocumentDelete is the builder for deleting a Document entity.

View File

@@ -12,11 +12,11 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// DocumentQuery is the builder for querying Document entities. // DocumentQuery is the builder for querying Document entities.

View File

@@ -12,11 +12,11 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// DocumentUpdate is the builder for updating Document entities. // DocumentUpdate is the builder for updating Document entities.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
) )
// DocumentToken is the model entity for the DocumentToken schema. // DocumentToken is the model entity for the DocumentToken schema.

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.

View File

@@ -11,8 +11,8 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
) )
// DocumentTokenCreate is the builder for creating a DocumentToken entity. // DocumentTokenCreate is the builder for creating a DocumentToken entity.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// DocumentTokenDelete is the builder for deleting a DocumentToken entity. // DocumentTokenDelete is the builder for deleting a DocumentToken entity.

View File

@@ -11,9 +11,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// DocumentTokenQuery is the builder for querying DocumentToken entities. // DocumentTokenQuery is the builder for querying DocumentToken entities.

View File

@@ -12,9 +12,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// DocumentTokenUpdate is the builder for updating DocumentToken entities. // DocumentTokenUpdate is the builder for updating DocumentToken entities.

View File

@@ -10,17 +10,17 @@ import (
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/hay-kot/homebox/backend/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/itemfield" "github.com/hay-kot/homebox/backend/internal/data/ent/itemfield"
"github.com/hay-kot/homebox/backend/ent/label" "github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// ent aliases to avoid import conflicts in user's code. // ent aliases to avoid import conflicts in user's code.

View File

@@ -5,12 +5,12 @@ package enttest
import ( import (
"context" "context"
"github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/data/ent"
// required by schema hooks. // required by schema hooks.
_ "github.com/hay-kot/homebox/backend/ent/runtime" _ "github.com/hay-kot/homebox/backend/internal/data/ent/runtime"
"entgo.io/ent/dialect/sql/schema" "entgo.io/ent/dialect/sql/schema"
"github.com/hay-kot/homebox/backend/ent/migrate" "github.com/hay-kot/homebox/backend/internal/data/ent/migrate"
) )
type ( type (

View File

@@ -9,7 +9,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
) )
// Group is the model entity for the Group schema. // Group is the model entity for the Group schema.

View File

@@ -124,6 +124,11 @@ const (
CurrencyEur Currency = "eur" CurrencyEur Currency = "eur"
CurrencyGbp Currency = "gbp" CurrencyGbp Currency = "gbp"
CurrencyJpy Currency = "jpy" CurrencyJpy Currency = "jpy"
CurrencyZar Currency = "zar"
CurrencyAud Currency = "aud"
CurrencyNok Currency = "nok"
CurrencySek Currency = "sek"
CurrencyDkk Currency = "dkk"
) )
func (c Currency) String() string { func (c Currency) String() string {
@@ -133,7 +138,7 @@ func (c Currency) String() string {
// CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save. // CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save.
func CurrencyValidator(c Currency) error { func CurrencyValidator(c Currency) error {
switch c { switch c {
case CurrencyUsd, CurrencyEur, CurrencyGbp, CurrencyJpy: case CurrencyUsd, CurrencyEur, CurrencyGbp, CurrencyJpy, CurrencyZar, CurrencyAud, CurrencyNok, CurrencySek, CurrencyDkk:
return nil return nil
default: default:
return fmt.Errorf("group: invalid enum value for currency field: %q", c) return fmt.Errorf("group: invalid enum value for currency field: %q", c)

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.

View File

@@ -11,13 +11,13 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/label" "github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// GroupCreate is the builder for creating a Group entity. // GroupCreate is the builder for creating a Group entity.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// GroupDelete is the builder for deleting a Group entity. // GroupDelete is the builder for deleting a Group entity.

View File

@@ -12,14 +12,14 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/label" "github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// GroupQuery is the builder for querying Group entities. // GroupQuery is the builder for querying Group entities.

View File

@@ -12,14 +12,14 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/label" "github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
) )
// GroupUpdate is the builder for updating Group entities. // GroupUpdate is the builder for updating Group entities.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
) )
// GroupInvitationToken is the model entity for the GroupInvitationToken schema. // GroupInvitationToken is the model entity for the GroupInvitationToken schema.

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.

View File

@@ -11,8 +11,8 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
) )
// GroupInvitationTokenCreate is the builder for creating a GroupInvitationToken entity. // GroupInvitationTokenCreate is the builder for creating a GroupInvitationToken entity.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// GroupInvitationTokenDelete is the builder for deleting a GroupInvitationToken entity. // GroupInvitationTokenDelete is the builder for deleting a GroupInvitationToken entity.

View File

@@ -11,9 +11,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// GroupInvitationTokenQuery is the builder for querying GroupInvitationToken entities. // GroupInvitationTokenQuery is the builder for querying GroupInvitationToken entities.

View File

@@ -12,9 +12,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken" "github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
) )
// GroupInvitationTokenUpdate is the builder for updating GroupInvitationToken entities. // GroupInvitationTokenUpdate is the builder for updating GroupInvitationToken entities.

View File

@@ -6,7 +6,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/data/ent"
) )
// The AttachmentFunc type is an adapter to allow the use of ordinary // The AttachmentFunc type is an adapter to allow the use of ordinary

View File

@@ -9,9 +9,9 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/item" "github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/location"
) )
// Item is the model entity for the Item schema. // Item is the model entity for the Item schema.
@@ -35,6 +35,8 @@ type Item struct {
Quantity int `json:"quantity,omitempty"` Quantity int `json:"quantity,omitempty"`
// Insured holds the value of the "insured" field. // Insured holds the value of the "insured" field.
Insured bool `json:"insured,omitempty"` Insured bool `json:"insured,omitempty"`
// Archived holds the value of the "archived" field.
Archived bool `json:"archived,omitempty"`
// SerialNumber holds the value of the "serial_number" field. // SerialNumber holds the value of the "serial_number" field.
SerialNumber string `json:"serial_number,omitempty"` SerialNumber string `json:"serial_number,omitempty"`
// ModelNumber holds the value of the "model_number" field. // ModelNumber holds the value of the "model_number" field.
@@ -65,11 +67,16 @@ type Item struct {
// The values are being populated by the ItemQuery when eager-loading is set. // The values are being populated by the ItemQuery when eager-loading is set.
Edges ItemEdges `json:"edges"` Edges ItemEdges `json:"edges"`
group_items *uuid.UUID group_items *uuid.UUID
item_children *uuid.UUID
location_items *uuid.UUID location_items *uuid.UUID
} }
// ItemEdges holds the relations/edges for other nodes in the graph. // ItemEdges holds the relations/edges for other nodes in the graph.
type ItemEdges struct { type ItemEdges struct {
// Parent holds the value of the parent edge.
Parent *Item `json:"parent,omitempty"`
// Children holds the value of the children edge.
Children []*Item `json:"children,omitempty"`
// Group holds the value of the group edge. // Group holds the value of the group edge.
Group *Group `json:"group,omitempty"` Group *Group `json:"group,omitempty"`
// Label holds the value of the label edge. // Label holds the value of the label edge.
@@ -82,13 +89,35 @@ type ItemEdges struct {
Attachments []*Attachment `json:"attachments,omitempty"` Attachments []*Attachment `json:"attachments,omitempty"`
// loadedTypes holds the information for reporting if a // loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not. // type was loaded (or requested) in eager-loading or not.
loadedTypes [5]bool loadedTypes [7]bool
}
// ParentOrErr returns the Parent value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e ItemEdges) ParentOrErr() (*Item, error) {
if e.loadedTypes[0] {
if e.Parent == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: item.Label}
}
return e.Parent, nil
}
return nil, &NotLoadedError{edge: "parent"}
}
// ChildrenOrErr returns the Children value or an error if the edge
// was not loaded in eager-loading.
func (e ItemEdges) ChildrenOrErr() ([]*Item, error) {
if e.loadedTypes[1] {
return e.Children, nil
}
return nil, &NotLoadedError{edge: "children"}
} }
// GroupOrErr returns the Group value or an error if the edge // GroupOrErr returns the Group value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found. // was not loaded in eager-loading, or loaded but was not found.
func (e ItemEdges) GroupOrErr() (*Group, error) { func (e ItemEdges) GroupOrErr() (*Group, error) {
if e.loadedTypes[0] { if e.loadedTypes[2] {
if e.Group == nil { if e.Group == nil {
// Edge was loaded but was not found. // Edge was loaded but was not found.
return nil, &NotFoundError{label: group.Label} return nil, &NotFoundError{label: group.Label}
@@ -101,7 +130,7 @@ func (e ItemEdges) GroupOrErr() (*Group, error) {
// LabelOrErr returns the Label value or an error if the edge // LabelOrErr returns the Label value or an error if the edge
// was not loaded in eager-loading. // was not loaded in eager-loading.
func (e ItemEdges) LabelOrErr() ([]*Label, error) { func (e ItemEdges) LabelOrErr() ([]*Label, error) {
if e.loadedTypes[1] { if e.loadedTypes[3] {
return e.Label, nil return e.Label, nil
} }
return nil, &NotLoadedError{edge: "label"} return nil, &NotLoadedError{edge: "label"}
@@ -110,7 +139,7 @@ func (e ItemEdges) LabelOrErr() ([]*Label, error) {
// LocationOrErr returns the Location value or an error if the edge // LocationOrErr returns the Location value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found. // was not loaded in eager-loading, or loaded but was not found.
func (e ItemEdges) LocationOrErr() (*Location, error) { func (e ItemEdges) LocationOrErr() (*Location, error) {
if e.loadedTypes[2] { if e.loadedTypes[4] {
if e.Location == nil { if e.Location == nil {
// Edge was loaded but was not found. // Edge was loaded but was not found.
return nil, &NotFoundError{label: location.Label} return nil, &NotFoundError{label: location.Label}
@@ -123,7 +152,7 @@ func (e ItemEdges) LocationOrErr() (*Location, error) {
// FieldsOrErr returns the Fields value or an error if the edge // FieldsOrErr returns the Fields value or an error if the edge
// was not loaded in eager-loading. // was not loaded in eager-loading.
func (e ItemEdges) FieldsOrErr() ([]*ItemField, error) { func (e ItemEdges) FieldsOrErr() ([]*ItemField, error) {
if e.loadedTypes[3] { if e.loadedTypes[5] {
return e.Fields, nil return e.Fields, nil
} }
return nil, &NotLoadedError{edge: "fields"} return nil, &NotLoadedError{edge: "fields"}
@@ -132,7 +161,7 @@ func (e ItemEdges) FieldsOrErr() ([]*ItemField, error) {
// AttachmentsOrErr returns the Attachments value or an error if the edge // AttachmentsOrErr returns the Attachments value or an error if the edge
// was not loaded in eager-loading. // was not loaded in eager-loading.
func (e ItemEdges) AttachmentsOrErr() ([]*Attachment, error) { func (e ItemEdges) AttachmentsOrErr() ([]*Attachment, error) {
if e.loadedTypes[4] { if e.loadedTypes[6] {
return e.Attachments, nil return e.Attachments, nil
} }
return nil, &NotLoadedError{edge: "attachments"} return nil, &NotLoadedError{edge: "attachments"}
@@ -143,7 +172,7 @@ func (*Item) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns)) values := make([]any, len(columns))
for i := range columns { for i := range columns {
switch columns[i] { switch columns[i] {
case item.FieldInsured, item.FieldLifetimeWarranty: case item.FieldInsured, item.FieldArchived, item.FieldLifetimeWarranty:
values[i] = new(sql.NullBool) values[i] = new(sql.NullBool)
case item.FieldPurchasePrice, item.FieldSoldPrice: case item.FieldPurchasePrice, item.FieldSoldPrice:
values[i] = new(sql.NullFloat64) values[i] = new(sql.NullFloat64)
@@ -157,7 +186,9 @@ func (*Item) scanValues(columns []string) ([]any, error) {
values[i] = new(uuid.UUID) values[i] = new(uuid.UUID)
case item.ForeignKeys[0]: // group_items case item.ForeignKeys[0]: // group_items
values[i] = &sql.NullScanner{S: new(uuid.UUID)} values[i] = &sql.NullScanner{S: new(uuid.UUID)}
case item.ForeignKeys[1]: // location_items case item.ForeignKeys[1]: // item_children
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
case item.ForeignKeys[2]: // location_items
values[i] = &sql.NullScanner{S: new(uuid.UUID)} values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default: default:
return nil, fmt.Errorf("unexpected column %q for type Item", columns[i]) return nil, fmt.Errorf("unexpected column %q for type Item", columns[i])
@@ -228,6 +259,12 @@ func (i *Item) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
i.Insured = value.Bool i.Insured = value.Bool
} }
case item.FieldArchived:
if value, ok := values[j].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field archived", values[j])
} else if value.Valid {
i.Archived = value.Bool
}
case item.FieldSerialNumber: case item.FieldSerialNumber:
if value, ok := values[j].(*sql.NullString); !ok { if value, ok := values[j].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field serial_number", values[j]) return fmt.Errorf("unexpected type %T for field serial_number", values[j])
@@ -314,6 +351,13 @@ func (i *Item) assignValues(columns []string, values []any) error {
*i.group_items = *value.S.(*uuid.UUID) *i.group_items = *value.S.(*uuid.UUID)
} }
case item.ForeignKeys[1]: case item.ForeignKeys[1]:
if value, ok := values[j].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field item_children", values[j])
} else if value.Valid {
i.item_children = new(uuid.UUID)
*i.item_children = *value.S.(*uuid.UUID)
}
case item.ForeignKeys[2]:
if value, ok := values[j].(*sql.NullScanner); !ok { if value, ok := values[j].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field location_items", values[j]) return fmt.Errorf("unexpected type %T for field location_items", values[j])
} else if value.Valid { } else if value.Valid {
@@ -325,6 +369,16 @@ func (i *Item) assignValues(columns []string, values []any) error {
return nil return nil
} }
// QueryParent queries the "parent" edge of the Item entity.
func (i *Item) QueryParent() *ItemQuery {
return (&ItemClient{config: i.config}).QueryParent(i)
}
// QueryChildren queries the "children" edge of the Item entity.
func (i *Item) QueryChildren() *ItemQuery {
return (&ItemClient{config: i.config}).QueryChildren(i)
}
// QueryGroup queries the "group" edge of the Item entity. // QueryGroup queries the "group" edge of the Item entity.
func (i *Item) QueryGroup() *GroupQuery { func (i *Item) QueryGroup() *GroupQuery {
return (&ItemClient{config: i.config}).QueryGroup(i) return (&ItemClient{config: i.config}).QueryGroup(i)
@@ -397,6 +451,9 @@ func (i *Item) String() string {
builder.WriteString("insured=") builder.WriteString("insured=")
builder.WriteString(fmt.Sprintf("%v", i.Insured)) builder.WriteString(fmt.Sprintf("%v", i.Insured))
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("archived=")
builder.WriteString(fmt.Sprintf("%v", i.Archived))
builder.WriteString(", ")
builder.WriteString("serial_number=") builder.WriteString("serial_number=")
builder.WriteString(i.SerialNumber) builder.WriteString(i.SerialNumber)
builder.WriteString(", ") builder.WriteString(", ")

View File

@@ -29,6 +29,8 @@ const (
FieldQuantity = "quantity" FieldQuantity = "quantity"
// FieldInsured holds the string denoting the insured field in the database. // FieldInsured holds the string denoting the insured field in the database.
FieldInsured = "insured" FieldInsured = "insured"
// FieldArchived holds the string denoting the archived field in the database.
FieldArchived = "archived"
// FieldSerialNumber holds the string denoting the serial_number field in the database. // FieldSerialNumber holds the string denoting the serial_number field in the database.
FieldSerialNumber = "serial_number" FieldSerialNumber = "serial_number"
// FieldModelNumber holds the string denoting the model_number field in the database. // FieldModelNumber holds the string denoting the model_number field in the database.
@@ -55,6 +57,10 @@ const (
FieldSoldPrice = "sold_price" FieldSoldPrice = "sold_price"
// FieldSoldNotes holds the string denoting the sold_notes field in the database. // FieldSoldNotes holds the string denoting the sold_notes field in the database.
FieldSoldNotes = "sold_notes" FieldSoldNotes = "sold_notes"
// EdgeParent holds the string denoting the parent edge name in mutations.
EdgeParent = "parent"
// EdgeChildren holds the string denoting the children edge name in mutations.
EdgeChildren = "children"
// EdgeGroup holds the string denoting the group edge name in mutations. // EdgeGroup holds the string denoting the group edge name in mutations.
EdgeGroup = "group" EdgeGroup = "group"
// EdgeLabel holds the string denoting the label edge name in mutations. // EdgeLabel holds the string denoting the label edge name in mutations.
@@ -67,6 +73,14 @@ const (
EdgeAttachments = "attachments" EdgeAttachments = "attachments"
// Table holds the table name of the item in the database. // Table holds the table name of the item in the database.
Table = "items" Table = "items"
// ParentTable is the table that holds the parent relation/edge.
ParentTable = "items"
// ParentColumn is the table column denoting the parent relation/edge.
ParentColumn = "item_children"
// ChildrenTable is the table that holds the children relation/edge.
ChildrenTable = "items"
// ChildrenColumn is the table column denoting the children relation/edge.
ChildrenColumn = "item_children"
// GroupTable is the table that holds the group relation/edge. // GroupTable is the table that holds the group relation/edge.
GroupTable = "items" GroupTable = "items"
// GroupInverseTable is the table name for the Group entity. // GroupInverseTable is the table name for the Group entity.
@@ -113,6 +127,7 @@ var Columns = []string{
FieldNotes, FieldNotes,
FieldQuantity, FieldQuantity,
FieldInsured, FieldInsured,
FieldArchived,
FieldSerialNumber, FieldSerialNumber,
FieldModelNumber, FieldModelNumber,
FieldManufacturer, FieldManufacturer,
@@ -132,6 +147,7 @@ var Columns = []string{
// table and are not defined as standalone fields in the schema. // table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{ var ForeignKeys = []string{
"group_items", "group_items",
"item_children",
"location_items", "location_items",
} }
@@ -175,6 +191,8 @@ var (
DefaultQuantity int DefaultQuantity int
// DefaultInsured holds the default value on creation for the "insured" field. // DefaultInsured holds the default value on creation for the "insured" field.
DefaultInsured bool DefaultInsured bool
// DefaultArchived holds the default value on creation for the "archived" field.
DefaultArchived bool
// SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save. // SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save.
SerialNumberValidator func(string) error SerialNumberValidator func(string) error
// ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save. // ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save.

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