Compare commits

..

31 commits

Author SHA1 Message Date
Hayden
3cfd274212
fix failing tests 2023-03-06 21:06:18 -09:00
Hayden
e71146df3d
set validate + order fields by name 2023-03-06 20:55:40 -09:00
Hayden
8c72d66d87
add custom validator 2023-03-06 20:55:29 -09:00
Hayden
b6c2db0e83
refactor 2023-03-06 11:46:01 -09:00
Hayden
883468e04c
refactor schema edges 2023-03-06 11:43:56 -09:00
Hayden
ccd40ffcac
audit and update documentation + improve format 2023-03-06 11:21:39 -09:00
Hayden
9c6421a9ff
include link to API docs 2023-03-06 10:40:35 -09:00
Hayden
c18d72b8a9
delete test file 2023-03-06 10:38:44 -09:00
Hayden
737007b156
support group edges via scaffold 2023-03-05 21:56:53 -09:00
Hayden
9fa4da819f
refactor schema folder 2023-03-05 21:47:31 -09:00
Hayden
77fb37ebf4
remove notifier service 2023-03-05 21:30:44 -09:00
Hayden
7167d200ea
add scaffold schema reference 2023-03-05 21:30:16 -09:00
Hayden
e1f9c2997b
improve documentation 2023-03-05 21:21:07 -09:00
Hayden
c2f432cb4d
use badges instead of text 2023-03-05 21:21:07 -09:00
Hayden
b6f44dfe58
wip: notifiers UI 2023-03-05 21:21:06 -09:00
Hayden
30b1879c35
reverse checkbox label display 2023-03-05 21:21:06 -09:00
Hayden
913912a1d9
go tidy 2023-03-05 21:21:06 -09:00
Hayden
fa069845fa
update errors to fix validation panic 2023-03-05 21:21:06 -09:00
Hayden
882f86f6f4
new routes for notifiers 2023-03-05 21:21:06 -09:00
Hayden
d79d0b45bf
refactor adapters to fit more common use cases 2023-03-05 21:21:06 -09:00
Hayden
b0a9c510ad
introduce experimental adapter pattern for hdlrs 2023-03-05 21:21:05 -09:00
Hayden
b82fbb01f5
notifier repo 2023-03-05 21:21:05 -09:00
Hayden
4d744c0e4b
new mapper helpers 2023-03-05 21:21:05 -09:00
Hayden
9f59a5d393
db migration 2023-03-05 21:21:05 -09:00
Hayden
37857682e6
add group_id to notifier 2023-03-05 21:21:05 -09:00
Hayden
43b34e2899
go mod tidy 2023-03-05 21:21:05 -09:00
Hayden
65d0b8fe4f
gen: migrations 2023-03-05 21:20:31 -09:00
Hayden
791d6979b6
gen: ent code 2023-03-05 21:20:30 -09:00
Hayden
b491ff9ec1
update schema files 2023-03-05 21:20:30 -09:00
Hayden
f169f1c710
wip: shoutrrr wrapper (may remove) 2023-03-05 21:20:30 -09:00
Hayden
4725d9c823
introduce scaffold for new models 2023-03-05 21:20:30 -09:00
344 changed files with 12659 additions and 25844 deletions

View file

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

View file

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

View file

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

View file

@ -9,11 +9,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: pnpm/action-setup@v3.0.0 - uses: pnpm/action-setup@v2.2.4
with: with:
version: 6.0.2 version: 6.0.2
@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
@ -44,15 +44,15 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: "1.21" go-version: 1.19
- uses: actions/setup-node@v4 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
- uses: pnpm/action-setup@v3.0.0 - uses: pnpm/action-setup@v2.2.4
with: with:
version: 6.0.2 version: 6.0.2

View file

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

View file

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

View file

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

View file

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

8
.gitignore vendored
View file

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

View file

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

View file

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

View file

@ -1,48 +1,50 @@
# Build Nuxt # Build Nuxt
FROM r.batts.cloud/nodejs:18 as frontend-builder FROM node:17-alpine as frontend-builder
WORKDIR /app WORKDIR /app
RUN npm install -g pnpm@latest-9 RUN npm install -g pnpm
COPY frontend/package.json frontend/pnpm-lock.yaml ./ COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --shamefully-hoist RUN pnpm install --frozen-lockfile --shamefully-hoist
COPY frontend . COPY frontend .
RUN pnpm build RUN pnpm build
# Build API # Build API
FROM r.batts.cloud/golang:1.24 AS builder FROM golang:alpine AS builder
ARG BUILD_TIME ARG BUILD_TIME
ARG COMMIT ARG COMMIT
ARG VERSION ARG VERSION
RUN apt update && \ RUN apk update && \
apt install -y git build-essential gcc g++ apk upgrade && \
apk add --update git build-base gcc g++
WORKDIR /go/src/app
COPY ./backend . WORKDIR /go/src/app
RUN go get -d -v ./... COPY ./backend .
RUN rm -rf ./app/api/public RUN go get -d -v ./...
COPY --from=frontend-builder /app/.output/public ./app/api/static/public RUN rm -rf ./app/api/public
RUN CGO_ENABLED=0 GOOS=linux go build \ COPY --from=frontend-builder /app/.output/public ./app/api/static/public
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \ RUN CGO_ENABLED=1 GOOS=linux go build \
-o /go/bin/api \ -ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-v ./app/api/*.go -o /go/bin/api \
-v ./app/api/*.go
# Production Stage
FROM r.batts.cloud/debian:trixie # Production Stage
FROM alpine:latest
ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/ ENV HBOX_MODE=production
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1 ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
RUN mkdir /app
COPY --from=builder /go/bin/api /app RUN apk --no-cache add ca-certificates
RUN mkdir /app
RUN chmod +x /app/api COPY --from=builder /go/bin/api /app
LABEL Name=homebox Version=0.0.1 RUN chmod +x /app/api
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
EXPOSE 7745 LABEL Name=homebox Version=0.0.1
WORKDIR /app LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
VOLUME [ "/data" ] EXPOSE 7745
WORKDIR /app
ENTRYPOINT [ "/app/api" ] VOLUME [ "/data" ]
CMD [ "/data/config.yml" ]
ENTRYPOINT [ "/app/api" ]
CMD [ "/data/config.yml" ]

View file

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

View file

@ -16,28 +16,12 @@
[Configuration & Docker Compose](https://hay-kot.github.io/homebox/quick-start) [Configuration & Docker Compose](https://hay-kot.github.io/homebox/quick-start)
```bash ```bash
# If using the rootless image, ensure data docker run --name=homebox \
# folder has correct permissions --restart=always \
mkdir -p /path/to/data/folder --publish=3100:7745 \
chown 65532:65532 -R /path/to/data/folder ghcr.io/hay-kot/homebox:latest
docker run -d \
--name homebox \
--restart unless-stopped \
--publish 3100:7745 \
--env TZ=Europe/Bucharest \
--volume /path/to/data/folder/:/data \
ghcr.io/hay-kot/homebox:latest
# ghcr.io/hay-kot/homebox:latest-rootless
``` ```
<!-- CONTRIBUTING -->
## Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
If you are not a coder, you can still contribute financially. Financial contributions help me prioritize working on this project over others and helps me know that there is a real demand for project development.
<a href="https://www.buymeacoffee.com/haykot" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 107px !important;" ></a>
## Credits ## Credits
- Logo by [@lakotelman](https://github.com/lakotelman) - Logo by [@lakotelman](https://github.com/lakotelman)

View file

@ -1,8 +1,7 @@
version: "3" version: "3"
env: env:
HBOX_LOG_LEVEL: debug HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_pragma=busy_timeout=1000&_pragma=journal_mode=WAL&_fk=1
HBOX_OPTIONS_ALLOW_REGISTRATION: true HBOX_OPTIONS_ALLOW_REGISTRATION: true
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure" UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
tasks: tasks:
@ -13,77 +12,62 @@ tasks:
- cd backend && go mod tidy - cd backend && go mod tidy
- cd frontend && pnpm install --shamefully-hoist - cd frontend && pnpm install --shamefully-hoist
swag: generate:
desc: Generate swagger docs desc: |
dir: backend/app/api/static/ Generates collateral files from the backend project
vars: including swagger docs and typescripts type for the frontend
API: "../" deps:
INTERNAL: "../../../internal" - db:generate
PKGS: "../../../pkgs"
cmds:
- swag fmt --dir={{ .API }}
- swag init --dir={{ .API }},{{ .INTERNAL }}/core/services,{{ .INTERNAL }}/data/repo --parseDependency
sources:
- "./backend/app/api/**/*"
- "./backend/internal/data/**"
- "./backend/internal/core/services/**/*"
- "./backend/app/tools/typegen/main.go"
typescript-types:
desc: Generates typescript types from swagger definition
cmds: cmds:
- cd backend/app/api/static && swag fmt --dir=../
- 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/static/docs/swagger.json \ --path ./backend/app/api/static/docs/swagger.json \
--output ./frontend/lib/api/types --output ./frontend/lib/api/types
- go run ./backend/app/tools/typegen/main.go ./frontend/lib/api/types/data-contracts.ts - go run ./scripts/process-types/*.go ./frontend/lib/api/types/data-contracts.ts
sources:
- ./backend/app/tools/typegen/main.go
- ./backend/app/api/static/docs/swagger.json
generate:
deps:
- db:generate
cmds:
- task: swag
- task: typescript-types
- cp ./backend/app/api/static/docs/swagger.json docs/docs/api/openapi-2.0.json - cp ./backend/app/api/static/docs/swagger.json docs/docs/api/openapi-2.0.json
sources:
- "./backend/app/api/**/*"
- "./backend/internal/data/**"
- "./backend/internal/services/**/*"
- "./scripts/process-types.py"
generates:
- "./frontend/lib/api/types/data-contracts.ts"
- "./backend/internal/data/ent/schema"
- "./backend/app/api/static/docs/swagger.json"
- "./backend/app/api/static/docs/swagger.yaml"
go:run: go:run:
desc: Starts the backend api server (depends on generate task) desc: Starts the backend api server (depends on generate task)
dir: backend
deps: deps:
- generate - generate
cmds: cmds:
- go run ./app/api/ {{ .CLI_ARGS }} - cd backend && go run ./app/api/ {{ .CLI_ARGS }}
silent: false silent: false
go:test: go:test:
desc: Runs all go tests using gotestsum - supports passing gotestsum args desc: Runs all go tests using gotestsum - supports passing gotestsum args
dir: backend
cmds: cmds:
- gotestsum {{ .CLI_ARGS }} ./... - cd backend && gotestsum {{ .CLI_ARGS }} ./...
go:coverage: go:coverage:
desc: Runs all go tests with -race flag and generates a coverage report desc: Runs all go tests with -race flag and generates a coverage report
dir: backend
cmds: cmds:
- 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
go:tidy: go:tidy:
desc: Runs go mod tidy on the backend desc: Runs go mod tidy on the backend
dir: backend
cmds: cmds:
- go mod tidy - cd backend && go mod tidy
go:lint: go:lint:
desc: Runs golangci-lint desc: Runs golangci-lint
dir: backend
cmds: cmds:
- golangci-lint run ./... - cd backend && golangci-lint run ./...
go:all: go:all:
desc: Runs all go test and lint related tasks desc: Runs all go test and lint related tasks
@ -94,18 +78,19 @@ tasks:
go:build: go:build:
desc: Builds the backend binary desc: Builds the backend binary
dir: backend
cmds: cmds:
- go build -o ../build/backend ./app/api - 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
dir: backend/internal/
cmds: cmds:
- | - |
go generate ./... cd backend/internal/ && go generate ./... \
--template=./data/ent/schema/templates/has_id.tmpl
sources: sources:
- "./backend/internal/data/ent/schema/**/*" - "./backend/internal/data/ent/schema/**/*"
generates:
- "./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
@ -116,27 +101,23 @@ tasks:
ui:watch: ui:watch:
desc: Starts the vitest test runner in watch mode desc: Starts the vitest test runner in watch mode
dir: frontend
cmds: cmds:
- pnpm run test:watch - cd frontend && pnpm run test:watch
ui:dev: ui:dev:
desc: Run frontend development server desc: Run frontend development server
dir: frontend
cmds: cmds:
- pnpm dev - cd frontend && pnpm dev
ui:fix: ui:fix:
desc: Runs prettier and eslint on the frontend desc: Runs prettier and eslint on the frontend
dir: frontend
cmds: cmds:
- pnpm run lint:fix - cd frontend && pnpm run lint:fix
ui:check: ui:check:
desc: Runs type checking desc: Runs type checking
dir: frontend
cmds: cmds:
- pnpm run typecheck - cd frontend && pnpm run typecheck
test:ci: test:ci:
desc: Runs end-to-end test on a live server (only for use in CI) desc: Runs end-to-end test on a live server (only for use in CI)
@ -154,4 +135,4 @@ tasks:
- task: go:all - task: go:all
- task: ui:check - task: ui:check
- task: ui:fix - task: ui:fix
- task: test:ci - task: test:ci

2
backend/.gitignore vendored
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"strings" "strings"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -16,12 +15,9 @@ func (a *app) SetupDemo() {
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,, ,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,, ,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,,
,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,, ,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,,
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,, ,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
` `
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
registration := services.UserRegistration{ registration := services.UserRegistration{
Email: "demo@example.com", Email: "demo@example.com",
Name: "Demo", Name: "Demo",
@ -29,34 +25,21 @@ func (a *app) SetupDemo() {
} }
// First check if we've already setup a demo user and skip if so // First check if we've already setup a demo user and skip if so
log.Debug().Msg("Checking if demo user already exists") _, err := a.services.User.Login(context.Background(), registration.Email, registration.Password)
_, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err == nil { if err == nil {
log.Info().Msg("Demo user already exists, skipping setup")
return return
} }
log.Debug().Msg("Demo user does not exist, setting up demo") _, err = a.services.User.RegisterUser(context.Background(), registration)
_, err = a.services.User.RegisterUser(ctx, registration)
if err != nil { if err != nil {
log.Err(err).Msg("Failed to register demo user") log.Err(err).Msg("Failed to register demo user")
log.Fatal().Msg("Failed to setup demo") log.Fatal().Msg("Failed to setup demo")
} }
token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false) token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password)
if err != nil { self, _ := a.services.User.GetSelf(context.Background(), token.Raw)
log.Err(err).Msg("Failed to login demo user")
log.Fatal().Msg("Failed to setup demo")
return
}
self, err := a.services.User.GetSelf(ctx, token.Raw)
if err != nil {
log.Err(err).Msg("Failed to get self")
log.Fatal().Msg("Failed to setup demo")
return
}
_, err = a.services.Items.CsvImport(ctx, self.GroupID, strings.NewReader(csvText)) _, err = a.services.Items.CsvImport(context.Background(), self.GroupID, strings.NewReader(csvText))
if err != nil { if err != nil {
log.Err(err).Msg("Failed to import CSV") log.Err(err).Msg("Failed to import CSV")
log.Fatal().Msg("Failed to setup demo") log.Fatal().Msg("Failed to setup demo")

View file

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

View file

@ -1,38 +1,13 @@
// Package v1 provides the API handlers for version 1 of the API.
package v1 package v1
import ( import (
"encoding/json"
"net/http" "net/http"
"time"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
"github.com/olahol/melody"
) )
type Results[T any] struct {
Items []T `json:"items"`
}
func WrapResults[T any](items []T) Results[T] {
return Results[T]{Items: items}
}
type Wrapped struct {
Item interface{} `json:"item"`
}
func Wrap(v any) Wrapped {
return Wrapped{Item: v}
}
func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) { func WithMaxUploadSize(maxUploadSize int64) func(*V1Controller) {
return func(ctrl *V1Controller) { return func(ctrl *V1Controller) {
ctrl.maxUploadSize = maxUploadSize ctrl.maxUploadSize = maxUploadSize
@ -51,20 +26,12 @@ func WithRegistration(allowRegistration bool) func(*V1Controller) {
} }
} }
func WithSecureCookies(secure bool) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.cookieSecure = secure
}
}
type V1Controller struct { type V1Controller struct {
cookieSecure bool
repo *repo.AllRepos repo *repo.AllRepos
svc *services.AllServices svc *services.AllServices
maxUploadSize int64 maxUploadSize int64
isDemo bool isDemo bool
allowRegistration bool allowRegistration bool
bus *eventbus.EventBus
} }
type ( type (
@ -76,7 +43,7 @@ type (
BuildTime string `json:"buildTime"` BuildTime string `json:"buildTime"`
} }
APISummary struct { ApiSummary struct {
Healthy bool `json:"health"` Healthy bool `json:"health"`
Versions []string `json:"versions"` Versions []string `json:"versions"`
Title string `json:"title"` Title string `json:"title"`
@ -87,18 +54,17 @@ type (
} }
) )
func BaseURLFunc(prefix string) func(s string) string { func BaseUrlFunc(prefix string) func(s string) string {
return func(s string) string { return func(s string) string {
return prefix + "/v1" + s return prefix + "/v1" + s
} }
} }
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, options ...func(*V1Controller)) *V1Controller { func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, options ...func(*V1Controller)) *V1Controller {
ctrl := &V1Controller{ ctrl := &V1Controller{
repo: repos, repo: repos,
svc: svc, svc: svc,
allowRegistration: true, allowRegistration: true,
bus: bus,
} }
for _, opt := range options { for _, opt := range options {
@ -113,101 +79,17 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *event
// @Summary Application Info // @Summary Application Info
// @Tags Base // @Tags Base
// @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) errchain.HandlerFunc { func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
return server.JSON(w, http.StatusOK, APISummary{ return server.Respond(w, http.StatusOK, ApiSummary{
Healthy: ready(), Healthy: ready(),
Title: "Homebox", Title: "Homebox",
Message: "Track, Manage, and Organize your Things", Message: "Track, Manage, and Organize your shit",
Build: build, Build: build,
Demo: ctrl.isDemo, Demo: ctrl.isDemo,
AllowRegistration: ctrl.allowRegistration, AllowRegistration: ctrl.allowRegistration,
}) })
} }
} }
// HandleCurrency godoc
//
// @Summary Currency
// @Tags Base
// @Produce json
// @Success 200 {object} currencies.Currency
// @Router /v1/currency [GET]
func (ctrl *V1Controller) HandleCurrency() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// Set Cache for 10 Minutes
w.Header().Set("Cache-Control", "max-age=600")
return server.JSON(w, http.StatusOK, ctrl.svc.Currencies.Slice())
}
}
func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc {
type eventMsg struct {
Event string `json:"event"`
}
m := melody.New()
m.HandleConnect(func(s *melody.Session) {
auth := services.NewContext(s.Request.Context())
s.Set("gid", auth.GID)
})
factory := func(e string) func(data any) {
return func(data any) {
eventData, ok := data.(eventbus.GroupMutationEvent)
if !ok {
log.Log().Msgf("invalid event data: %v", data)
return
}
msg := &eventMsg{Event: e}
jsonBytes, err := json.Marshal(msg)
if err != nil {
log.Log().Msgf("error marshling event data %v: %v", data, err)
return
}
_ = m.BroadcastFilter(jsonBytes, func(s *melody.Session) bool {
groupIDStr, ok := s.Get("gid")
if !ok {
return false
}
GID := groupIDStr.(uuid.UUID)
return GID == eventData.GID
})
}
}
ctrl.bus.Subscribe(eventbus.EventLabelMutation, factory("label.mutation"))
ctrl.bus.Subscribe(eventbus.EventLocationMutation, factory("location.mutation"))
ctrl.bus.Subscribe(eventbus.EventItemMutation, factory("item.mutation"))
// Persistent asynchronous ticker that keeps all websocket connections alive with periodic pings.
go func() {
const interval = 10 * time.Second
ping := time.NewTicker(interval)
defer ping.Stop()
for range ping.C {
msg := &eventMsg{Event: "ping"}
pingBytes, err := json.Marshal(msg)
if err != nil {
log.Log().Msgf("error marshaling ping: %v", err)
} else {
_ = m.Broadcast(pingBytes)
}
}
}()
return func(w http.ResponseWriter, r *http.Request) error {
return m.HandleRequest(w, r)
}
}

View file

@ -7,8 +7,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/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/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -16,7 +15,7 @@ type ActionAmountResult struct {
Completed int `json:"completed"` Completed int `json:"completed"`
} }
func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, error)) errchain.HandlerFunc { func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, error)) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
@ -26,7 +25,7 @@ func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int,
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted}) return server.Respond(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
} }
} }
@ -39,7 +38,7 @@ func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int,
// @Success 200 {object} ActionAmountResult // @Success 200 {object} ActionAmountResult
// @Router /v1/actions/ensure-asset-ids [Post] // @Router /v1/actions/ensure-asset-ids [Post]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleEnsureAssetID() errchain.HandlerFunc { func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc {
return actionHandlerFactory("ensure asset IDs", ctrl.svc.Items.EnsureAssetID) return actionHandlerFactory("ensure asset IDs", ctrl.svc.Items.EnsureAssetID)
} }
@ -52,7 +51,7 @@ func (ctrl *V1Controller) HandleEnsureAssetID() errchain.HandlerFunc {
// @Success 200 {object} ActionAmountResult // @Success 200 {object} ActionAmountResult
// @Router /v1/actions/ensure-import-refs [Post] // @Router /v1/actions/ensure-import-refs [Post]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleEnsureImportRefs() errchain.HandlerFunc { func (ctrl *V1Controller) HandleEnsureImportRefs() server.HandlerFunc {
return actionHandlerFactory("ensure import refs", ctrl.svc.Items.EnsureImportRef) return actionHandlerFactory("ensure import refs", ctrl.svc.Items.EnsureImportRef)
} }
@ -65,19 +64,6 @@ func (ctrl *V1Controller) HandleEnsureImportRefs() errchain.HandlerFunc {
// @Success 200 {object} ActionAmountResult // @Success 200 {object} ActionAmountResult
// @Router /v1/actions/zero-item-time-fields [Post] // @Router /v1/actions/zero-item-time-fields [Post]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemDateZeroOut() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemDateZeroOut() server.HandlerFunc {
return actionHandlerFactory("zero out date time", ctrl.repo.Items.ZeroOutTimeFields) return actionHandlerFactory("zero out date time", ctrl.repo.Items.ZeroOutTimeFields)
} }
// HandleSetPrimaryPhotos godoc
//
// @Summary Set Primary Photos
// @Description Sets the first photo of each item as the primary photo
// @Tags Actions
// @Produce json
// @Success 200 {object} ActionAmountResult
// @Router /v1/actions/set-primary-photos [Post]
// @Security Bearer
func (ctrl *V1Controller) HandleSetPrimaryPhotos() errchain.HandlerFunc {
return actionHandlerFactory("ensure asset IDs", ctrl.repo.Items.SetPrimaryPhotos)
}

View file

@ -9,8 +9,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/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/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -24,13 +23,13 @@ import (
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{} // @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/assets/{id} [GET] // @Router /v1/assets/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc { func (ctrl *V1Controller) HandleAssetGet() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
assetIDParam := chi.URLParam(r, "id") assetIdParam := chi.URLParam(r, "id")
assetIDParam = strings.ReplaceAll(assetIDParam, "-", "") // Remove dashes assetIdParam = strings.ReplaceAll(assetIdParam, "-", "") // Remove dashes
// Convert the asset ID to an int64 // Convert the asset ID to an int64
assetID, err := strconv.ParseInt(assetIDParam, 10, 64) assetId, err := strconv.ParseInt(assetIdParam, 10, 64)
if err != nil { if err != nil {
return err return err
} }
@ -39,7 +38,7 @@ func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc {
if pageParam != "" { if pageParam != "" {
page, err = strconv.ParseInt(pageParam, 10, 64) page, err = strconv.ParseInt(pageParam, 10, 64)
if err != nil { if err != nil {
return server.JSON(w, http.StatusBadRequest, "Invalid page number") return server.Respond(w, http.StatusBadRequest, "Invalid page number")
} }
} }
@ -48,15 +47,15 @@ func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc {
if pageSizeParam != "" { if pageSizeParam != "" {
pageSize, err = strconv.ParseInt(pageSizeParam, 10, 64) pageSize, err = strconv.ParseInt(pageSizeParam, 10, 64)
if err != nil { if err != nil {
return server.JSON(w, http.StatusBadRequest, "Invalid page size") return server.Respond(w, http.StatusBadRequest, "Invalid page size")
} }
} }
items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GID, repo.AssetID(assetID), int(page), int(pageSize)) items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GID, repo.AssetID(assetId), int(page), int(pageSize))
if err != nil { if err != nil {
log.Err(err).Msg("failed to get item") log.Err(err).Msg("failed to get item")
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusOK, items) return server.Respond(w, http.StatusOK, items)
} }
} }

View file

@ -3,23 +3,15 @@ package v1
import ( import (
"errors" "errors"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
"github.com/hay-kot/homebox/backend/internal/core/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/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
const (
cookieNameToken = "hb.auth.token"
cookieNameRemember = "hb.auth.remember"
cookieNameSession = "hb.auth.session"
)
type ( type (
TokenResponse struct { TokenResponse struct {
Token string `json:"token"` Token string `json:"token"`
@ -28,55 +20,11 @@ type (
} }
LoginForm struct { LoginForm struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
StayLoggedIn bool `json:"stayLoggedIn"`
} }
) )
type CookieContents struct {
Token string
ExpiresAt time.Time
Remember bool
}
func GetCookies(r *http.Request) (*CookieContents, error) {
cookie, err := r.Cookie(cookieNameToken)
if err != nil {
return nil, errors.New("authorization cookie is required")
}
rememberCookie, err := r.Cookie(cookieNameRemember)
if err != nil {
return nil, errors.New("remember cookie is required")
}
return &CookieContents{
Token: cookie.Value,
ExpiresAt: cookie.Expires,
Remember: rememberCookie.Value == "true",
}, nil
}
// AuthProvider is an interface that can be implemented by any authentication provider.
// to extend authentication methods for the API.
type AuthProvider interface {
// Name returns the name of the authentication provider. This should be a unique name.
// that is URL friendly.
//
// Example: "local", "ldap"
Name() string
// Authenticate is called when a user attempts to login to the API. The implementation
// should return an error if the user cannot be authenticated. If an error is returned
// the API controller will return a vague error message to the user.
//
// Authenticate should do the following:
//
// 1. Ensure that the user exists within the database (either create, or get)
// 2. On successful authentication, they must set the user cookies.
Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error)
}
// HandleAuthLogin godoc // HandleAuthLogin godoc
// //
// @Summary User Login // @Summary User Login
@ -85,43 +33,50 @@ type AuthProvider interface {
// @Accept application/json // @Accept application/json
// @Param username formData string false "string" example(admin@admin.com) // @Param username formData string false "string" example(admin@admin.com)
// @Param password formData string false "string" example(admin) // @Param password formData string false "string" example(admin)
// @Param payload body LoginForm true "Login Data"
// @Param provider query string false "auth provider"
// @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(ps ...AuthProvider) errchain.HandlerFunc { func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc {
if len(ps) == 0 {
panic("no auth providers provided")
}
providers := make(map[string]AuthProvider)
for _, p := range ps {
log.Info().Str("name", p.Name()).Msg("registering auth provider")
providers[p.Name()] = p
}
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
// Extract provider query loginForm := &LoginForm{}
provider := r.URL.Query().Get("provider")
if provider == "" { switch r.Header.Get("Content-Type") {
provider = "local" case server.ContentFormUrlEncoded:
err := r.ParseForm()
if err != nil {
return server.Respond(w, http.StatusBadRequest, server.Wrap(err))
}
loginForm.Username = r.PostFormValue("username")
loginForm.Password = r.PostFormValue("password")
case server.ContentJSON:
err := server.Decode(r, loginForm)
if err != nil {
log.Err(err).Msg("failed to decode login form")
}
default:
return server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
} }
// Get the provider if loginForm.Username == "" || loginForm.Password == "" {
p, ok := providers[provider] return validate.NewFieldErrors(
if !ok { validate.FieldError{
return validate.NewRequestError(errors.New("invalid auth provider"), http.StatusBadRequest) Field: "username",
Error: "username or password is empty",
},
validate.FieldError{
Field: "password",
Error: "username or password is empty",
},
)
} }
newToken, err := p.Authenticate(w, r) newToken, err := ctrl.svc.User.Login(r.Context(), strings.ToLower(loginForm.Username), loginForm.Password)
if err != nil { if err != nil {
log.Err(err).Msg("failed to authenticate") return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError)
return server.JSON(w, http.StatusInternalServerError, err.Error())
} }
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true) return server.Respond(w, http.StatusOK, TokenResponse{
return server.JSON(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw, Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt, ExpiresAt: newToken.ExpiresAt,
AttachmentToken: newToken.AttachmentToken, AttachmentToken: newToken.AttachmentToken,
@ -136,7 +91,7 @@ func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFu
// @Success 204 // @Success 204
// @Router /v1/users/logout [POST] // @Router /v1/users/logout [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc { func (ctrl *V1Controller) HandleAuthLogout() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context()) token := services.UseTokenCtx(r.Context())
if token == "" { if token == "" {
@ -148,12 +103,11 @@ func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
ctrl.unsetCookies(w, noPort(r.Host)) return server.Respond(w, http.StatusNoContent, nil)
return server.JSON(w, http.StatusNoContent, nil)
} }
} }
// HandleAuthRefresh godoc // HandleAuthLogout godoc
// //
// @Summary User Token Refresh // @Summary User Token Refresh
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token. // @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
@ -162,7 +116,7 @@ func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
// @Success 200 // @Success 200
// @Router /v1/users/refresh [GET] // @Router /v1/users/refresh [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc { func (ctrl *V1Controller) HandleAuthRefresh() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
requestToken := services.UseTokenCtx(r.Context()) requestToken := services.UseTokenCtx(r.Context())
if requestToken == "" { if requestToken == "" {
@ -174,78 +128,6 @@ func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc {
return validate.NewUnauthorizedError() return validate.NewUnauthorizedError()
} }
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, false) return server.Respond(w, http.StatusOK, newToken)
return server.JSON(w, http.StatusOK, newToken)
} }
} }
func noPort(host string) string {
return strings.Split(host, ":")[0]
}
func (ctrl *V1Controller) setCookies(w http.ResponseWriter, domain, token string, expires time.Time, remember bool) {
http.SetCookie(w, &http.Cookie{
Name: cookieNameRemember,
Value: strconv.FormatBool(remember),
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set HTTP only cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameToken,
Value: token,
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set Fake Session cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameSession,
Value: "true",
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: false,
Path: "/",
})
}
func (ctrl *V1Controller) unsetCookies(w http.ResponseWriter, domain string) {
http.SetCookie(w, &http.Cookie{
Name: cookieNameToken,
Value: "",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
http.SetCookie(w, &http.Cookie{
Name: cookieNameRemember,
Value: "false",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set Fake Session cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameSession,
Value: "false",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: false,
Path: "/",
})
}

View file

@ -7,13 +7,13 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/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/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/errchain" "github.com/rs/zerolog/log"
) )
type ( type (
GroupInvitationCreate struct { GroupInvitationCreate struct {
Uses int `json:"uses" validate:"required,min=1,max=100"` Uses int `json:"uses"`
ExpiresAt time.Time `json:"expiresAt"` ExpiresAt time.Time `json:"expiresAt"`
} }
@ -32,13 +32,8 @@ 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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc {
fn := func(r *http.Request) (repo.Group, error) { return ctrl.handleGroupGeneral()
auth := services.NewContext(r.Context())
return ctrl.repo.Groups.GroupByID(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK)
} }
// HandleGroupUpdate godoc // HandleGroupUpdate godoc
@ -50,21 +45,41 @@ func (ctrl *V1Controller) HandleGroupGet() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupUpdate() server.HandlerFunc {
fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) { return ctrl.handleGroupGeneral()
auth := services.NewContext(r.Context()) }
ok := ctrl.svc.Currencies.IsSupported(body.Currency) func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc {
if !ok { return func(w http.ResponseWriter, r *http.Request) error {
return repo.Group{}, validate.NewFieldErrors( ctx := services.NewContext(r.Context())
validate.NewFieldError("currency", "currency '"+body.Currency+"' is not supported"),
) switch r.Method {
case http.MethodGet:
group, err := ctrl.repo.Groups.GroupByID(ctx, ctx.GID)
if err != nil {
log.Err(err).Msg("failed to get group")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, group)
case http.MethodPut:
data := repo.GroupUpdate{}
if err := server.Decode(r, &data); err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
group, err := ctrl.svc.Group.UpdateGroup(ctx, data)
if err != nil {
log.Err(err).Msg("failed to update group")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, group)
} }
return ctrl.svc.Group.UpdateGroup(auth, body) return nil
} }
return adapters.Action(fn, http.StatusOK)
} }
// HandleGroupInvitationsCreate godoc // HandleGroupInvitationsCreate godoc
@ -76,22 +91,30 @@ func (ctrl *V1Controller) HandleGroupUpdate() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupInvitationsCreate() server.HandlerFunc {
fn := func(r *http.Request, body GroupInvitationCreate) (GroupInvitation, error) { return func(w http.ResponseWriter, r *http.Request) error {
if body.ExpiresAt.IsZero() { data := GroupInvitationCreate{}
body.ExpiresAt = time.Now().Add(time.Hour * 24) if err := server.Decode(r, &data); err != nil {
log.Err(err).Msg("failed to decode user registration data")
return validate.NewRequestError(err, http.StatusBadRequest)
} }
auth := services.NewContext(r.Context()) if data.ExpiresAt.IsZero() {
data.ExpiresAt = time.Now().Add(time.Hour * 24)
}
token, err := ctrl.svc.Group.NewInvitation(auth, body.Uses, body.ExpiresAt) ctx := services.NewContext(r.Context())
return GroupInvitation{ token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt)
if err != nil {
log.Err(err).Msg("failed to create new token")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, GroupInvitation{
Token: token, Token: token,
ExpiresAt: body.ExpiresAt, ExpiresAt: data.ExpiresAt,
Uses: body.Uses, Uses: data.Uses,
}, err })
} }
return adapters.Action(fn, http.StatusCreated)
} }

View file

@ -7,13 +7,10 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/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/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -27,11 +24,10 @@ import (
// @Param pageSize query int false "items per page" // @Param pageSize query int false "items per page"
// @Param labels query []string false "label Ids" collectionFormat(multi) // @Param labels query []string false "label Ids" collectionFormat(multi)
// @Param locations query []string false "location Ids" collectionFormat(multi) // @Param locations query []string false "location Ids" collectionFormat(multi)
// @Param parentIds query []string false "parent Ids" collectionFormat(multi)
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{} // @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET] // @Router /v1/items [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc {
extractQuery := func(r *http.Request) repo.ItemQuery { extractQuery := func(r *http.Request) repo.ItemQuery {
params := r.URL.Query() params := r.URL.Query()
@ -57,10 +53,8 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
Search: params.Get("q"), Search: params.Get("q"),
LocationIDs: queryUUIDList(params, "locations"), LocationIDs: queryUUIDList(params, "locations"),
LabelIDs: queryUUIDList(params, "labels"), LabelIDs: queryUUIDList(params, "labels"),
ParentItemIDs: queryUUIDList(params, "parentIds"),
IncludeArchived: queryBool(params.Get("includeArchived")), IncludeArchived: queryBool(params.Get("includeArchived")),
Fields: filterFieldItems(params["fields"]), Fields: filterFieldItems(params["fields"]),
OrderBy: params.Get("orderBy"),
} }
if strings.HasPrefix(v.Search, "#") { if strings.HasPrefix(v.Search, "#") {
@ -82,74 +76,43 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc {
items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r)) items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r))
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{ return server.Respond(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{
Items: []repo.ItemSummary{}, Items: []repo.ItemSummary{},
}) })
} }
log.Err(err).Msg("failed to get items") log.Err(err).Msg("failed to get items")
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusOK, items) return server.Respond(w, http.StatusOK, items)
} }
} }
// HandleItemFullPath godoc
//
// @Summary Get the full path of an item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 200 {object} []repo.ItemPath
// @Router /v1/items/{id}/path [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemFullPath() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) ([]repo.ItemPath, error) {
auth := services.NewContext(r.Context())
item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
if err != nil {
return nil, err
}
paths, err := ctrl.repo.Locations.PathForLoc(auth, auth.GID, item.Location.ID)
if err != nil {
return nil, err
}
if item.Parent != nil {
paths = append(paths, repo.ItemPath{
Type: repo.ItemTypeItem,
ID: item.Parent.ID,
Name: item.Parent.Name,
})
}
paths = append(paths, repo.ItemPath{
Type: repo.ItemTypeItem,
ID: item.ID,
Name: item.Name,
})
return paths, nil
}
return adapters.CommandID("id", fn, http.StatusOK)
}
// HandleItemsCreate godoc // HandleItemsCreate godoc
// //
// @Summary Create Item // @Summary Create Item
// @Tags Items // @Tags Items
// @Produce json // @Produce json
// @Param payload body repo.ItemCreate true "Item Data" // @Param payload body repo.ItemCreate true "Item Data"
// @Success 201 {object} repo.ItemSummary // @Success 200 {object} repo.ItemSummary
// @Router /v1/items [POST] // @Router /v1/items [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemsCreate() server.HandlerFunc {
fn := func(r *http.Request, body repo.ItemCreate) (repo.ItemOut, error) { return func(w http.ResponseWriter, r *http.Request) error {
return ctrl.svc.Items.Create(services.NewContext(r.Context()), body) createData := repo.ItemCreate{}
} if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode request body")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return adapters.Action(fn, http.StatusCreated) ctx := services.NewContext(r.Context())
item, err := ctrl.svc.Items.Create(ctx, createData)
if err != nil {
log.Err(err).Msg("failed to create item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, item)
}
} }
// HandleItemGet godocs // HandleItemGet godocs
@ -161,14 +124,8 @@ func (ctrl *V1Controller) HandleItemsCreate() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemGet() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.ItemOut, error) { return ctrl.handleItemsGeneral()
auth := services.NewContext(r.Context())
return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
} }
// HandleItemDelete godocs // HandleItemDelete godocs
@ -180,14 +137,8 @@ func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/items/{id} [DELETE] // @Router /v1/items/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemDelete() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) { return ctrl.handleItemsGeneral()
auth := services.NewContext(r.Context())
err := ctrl.repo.Items.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
} }
// HandleItemUpdate godocs // HandleItemUpdate godocs
@ -200,41 +151,50 @@ func (ctrl *V1Controller) HandleItemDelete() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemUpdate() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, body repo.ItemUpdate) (repo.ItemOut, error) { return ctrl.handleItemsGeneral()
auth := services.NewContext(r.Context())
body.ID = ID
return ctrl.repo.Items.UpdateByGroup(auth, auth.GID, body)
}
return adapters.ActionID("id", fn, http.StatusOK)
} }
// HandleItemPatch godocs func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc {
// return func(w http.ResponseWriter, r *http.Request) error {
// @Summary Update Item ctx := services.NewContext(r.Context())
// @Tags Items ID, err := ctrl.routeID(r)
// @Produce json
// @Param id path string true "Item ID"
// @Param payload body repo.ItemPatch true "Item Data"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [Patch]
// @Security Bearer
func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, body repo.ItemPatch) (repo.ItemOut, error) {
auth := services.NewContext(r.Context())
body.ID = ID
err := ctrl.repo.Items.Patch(auth, auth.GID, ID, body)
if err != nil { if err != nil {
return repo.ItemOut{}, err return err
} }
return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID) switch r.Method {
} case http.MethodGet:
items, err := ctrl.repo.Items.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to get item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, items)
case http.MethodDelete:
err = ctrl.repo.Items.DeleteByGroup(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
body := repo.ItemUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode request body")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.repo.Items.UpdateByGroup(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, result)
}
return adapters.ActionID("id", fn, http.StatusOK) return nil
}
} }
// HandleGetAllCustomFieldNames godocs // HandleGetAllCustomFieldNames godocs
@ -246,13 +206,17 @@ func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc {
// @Router /v1/items/fields [GET] // @Router /v1/items/fields [GET]
// @Success 200 {object} []string // @Success 200 {object} []string
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGetAllCustomFieldNames() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGetAllCustomFieldNames() server.HandlerFunc {
fn := func(r *http.Request) ([]string, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK) v, err := ctrl.repo.Items.GetAllCustomFieldNames(r.Context(), ctx.GID)
if err != nil {
return err
}
return server.Respond(w, http.StatusOK, v)
}
} }
// HandleGetAllCustomFieldValues godocs // HandleGetAllCustomFieldValues godocs
@ -264,17 +228,17 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldNames() errchain.HandlerFunc {
// @Router /v1/items/fields/values [GET] // @Router /v1/items/fields/values [GET]
// @Success 200 {object} []string // @Success 200 {object} []string
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGetAllCustomFieldValues() server.HandlerFunc {
type query struct { return func(w http.ResponseWriter, r *http.Request) error {
Field string `schema:"field" validate:"required"` ctx := services.NewContext(r.Context())
}
fn := func(r *http.Request, q query) ([]string, error) { v, err := ctrl.repo.Items.GetAllCustomFieldValues(r.Context(), ctx.GID, r.URL.Query().Get("field"))
auth := services.NewContext(r.Context()) if err != nil {
return ctrl.repo.Items.GetAllCustomFieldValues(auth, auth.GID, q.Field) return err
} }
return adapters.Query(fn, http.StatusOK) return server.Respond(w, http.StatusOK, v)
}
} }
// HandleItemsImport godocs // HandleItemsImport godocs
@ -286,7 +250,7 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { 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 {
@ -308,7 +272,7 @@ func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }
@ -319,7 +283,7 @@ func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc {
// @Success 200 {string} string "text/csv" // @Success 200 {string} string "text/csv"
// @Router /v1/items/export [GET] // @Router /v1/items/export [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemsExport() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
@ -331,9 +295,7 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
w.Header().Set("Content-Type", "text/tsv") w.Header().Set("Content-Type", "text/tsv")
w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.tsv") w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.tsv")
writer := csv.NewWriter(w) writer := csv.NewWriter(w)
writer.Comma = '\t'
return writer.WriteAll(csvData) return writer.WriteAll(csvData)
} }
} }

View file

@ -3,15 +3,12 @@ package v1
import ( import (
"errors" "errors"
"net/http" "net/http"
"path/filepath"
"strings"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -31,15 +28,16 @@ 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} validate.ErrorResponse // @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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { 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")
return validate.NewRequestError(errors.New("failed to parse multipart form"), http.StatusBadRequest) return validate.NewRequestError(errors.New("failed to parse multipart form"), http.StatusBadRequest)
} }
errs := validate.NewFieldErrors() errs := validate.NewFieldErrors()
@ -63,20 +61,12 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
} }
if !errs.Nil() { if !errs.Nil() {
return server.JSON(w, http.StatusUnprocessableEntity, errs) return server.Respond(w, http.StatusUnprocessableEntity, errs)
} }
attachmentType := r.FormValue("type") attachmentType := r.FormValue("type")
if attachmentType == "" { if attachmentType == "" {
// Attempt to auto-detect the type of the file attachmentType = attachment.TypeAttachment.String()
ext := filepath.Ext(attachmentName)
switch strings.ToLower(ext) {
case ".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff":
attachmentType = attachment.TypePhoto.String()
default:
attachmentType = attachment.TypeAttachment.String()
}
} }
id, err := ctrl.routeID(r) id, err := ctrl.routeID(r)
@ -98,7 +88,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusCreated, item) return server.Respond(w, http.StatusCreated, item)
} }
} }
@ -112,7 +102,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.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) HandleItemAttachmentGet() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentGet() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler return ctrl.handleItemAttachmentsHandler
} }
@ -125,7 +115,7 @@ func (ctrl *V1Controller) HandleItemAttachmentGet() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentDelete() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler return ctrl.handleItemAttachmentsHandler
} }
@ -139,7 +129,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemAttachmentUpdate() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler return ctrl.handleItemAttachmentsHandler
} }
@ -174,7 +164,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
// Update Attachment Handler // Update Attachment Handler
case http.MethodPut: case http.MethodPut:
@ -192,7 +182,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusOK, val) return server.Respond(w, http.StatusOK, val)
} }
return nil return nil

View file

@ -3,11 +3,12 @@ package v1
import ( import (
"net/http" "net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services" "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/data/repo"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
// HandleLabelsGetAll godoc // HandleLabelsGetAll godoc
@ -15,16 +16,19 @@ import (
// @Summary Get All Labels // @Summary Get All Labels
// @Tags Labels // @Tags Labels
// @Produce json // @Produce json
// @Success 200 {object} []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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelsGetAll() server.HandlerFunc {
fn := func(r *http.Request) ([]repo.LabelSummary, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) user := services.UseUserCtx(r.Context())
return ctrl.repo.Labels.GetAll(auth, auth.GID) labels, err := ctrl.repo.Labels.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("error getting labels")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, server.Results{Items: labels})
} }
return adapters.Command(fn, http.StatusOK)
} }
// HandleLabelsCreate godoc // HandleLabelsCreate godoc
@ -36,13 +40,23 @@ func (ctrl *V1Controller) HandleLabelsGetAll() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelsCreate() server.HandlerFunc {
fn := func(r *http.Request, data repo.LabelCreate) (repo.LabelOut, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) createData := repo.LabelCreate{}
return ctrl.repo.Labels.Create(auth, auth.GID, data) if err := server.Decode(r, &createData); err != nil {
} log.Err(err).Msg("error decoding label create data")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return adapters.Action(fn, http.StatusCreated) user := services.UseUserCtx(r.Context())
label, err := ctrl.repo.Labels.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("error creating label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, label)
}
} }
// HandleLabelDelete godocs // HandleLabelDelete godocs
@ -54,14 +68,8 @@ func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/labels/{id} [DELETE] // @Router /v1/labels/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelDelete() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) { return ctrl.handleLabelsGeneral()
auth := services.NewContext(r.Context())
err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
} }
// HandleLabelGet godocs // HandleLabelGet godocs
@ -73,13 +81,8 @@ func (ctrl *V1Controller) HandleLabelDelete() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelGet() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.LabelOut, error) { return ctrl.handleLabelsGeneral()
auth := services.NewContext(r.Context())
return ctrl.repo.Labels.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
} }
// HandleLabelUpdate godocs // HandleLabelUpdate godocs
@ -91,12 +94,55 @@ func (ctrl *V1Controller) HandleLabelGet() errchain.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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelUpdate() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) { return ctrl.handleLabelsGeneral()
auth := services.NewContext(r.Context()) }
data.ID = ID
return ctrl.repo.Labels.UpdateByGroup(auth, auth.GID, data) func (ctrl *V1Controller) handleLabelsGeneral() server.HandlerFunc {
} return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
return adapters.ActionID("id", fn, http.StatusOK) ID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
labels, err := ctrl.repo.Labels.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil {
if ent.IsNotFound(err) {
log.Err(err).
Str("id", ID.String()).
Msg("label not found")
return validate.NewRequestError(err, http.StatusNotFound)
}
log.Err(err).Msg("error getting label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, labels)
case http.MethodDelete:
err = ctrl.repo.Labels.DeleteByGroup(ctx, ctx.GID, ID)
if err != nil {
log.Err(err).Msg("error deleting label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
body := repo.LabelUpdate{}
if err := server.Decode(r, &body); err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.repo.Labels.UpdateByGroup(ctx, ctx.GID, body)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, result)
}
return nil
}
} }

View file

@ -3,11 +3,12 @@ package v1
import ( import (
"net/http" "net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/services" "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/data/repo"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
// HandleLocationTreeQuery godoc // HandleLocationTreeQuery godoc
@ -16,16 +17,31 @@ import (
// @Tags Locations // @Tags Locations
// @Produce json // @Produce json
// @Param withItems query bool false "include items in response tree" // @Param withItems query bool false "include items in response tree"
// @Success 200 {object} []repo.TreeItem // @Success 200 {object} server.Results{items=[]repo.TreeItem}
// @Router /v1/locations/tree [GET] // @Router /v1/locations/tree [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationTreeQuery() server.HandlerFunc {
fn := func(r *http.Request, query repo.TreeQuery) ([]repo.TreeItem, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) user := services.UseUserCtx(r.Context())
return ctrl.repo.Locations.Tree(auth, auth.GID, query)
}
return adapters.Query(fn, http.StatusOK) q := r.URL.Query()
withItems := queryBool(q.Get("withItems"))
locTree, err := ctrl.repo.Locations.Tree(
r.Context(),
user.GroupID,
repo.TreeQuery{
WithItems: withItems,
},
)
if err != nil {
log.Err(err).Msg("failed to get locations tree")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, server.Results{Items: locTree})
}
} }
// HandleLocationGetAll godoc // HandleLocationGetAll godoc
@ -34,16 +50,27 @@ func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc {
// @Tags Locations // @Tags Locations
// @Produce json // @Produce json
// @Param filterChildren query bool false "Filter locations with parents" // @Param filterChildren query bool false "Filter locations with parents"
// @Success 200 {object} []repo.LocationOutCount // @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
// @Router /v1/locations [GET] // @Router /v1/locations [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationGetAll() server.HandlerFunc {
fn := func(r *http.Request, q repo.LocationQuery) ([]repo.LocationOutCount, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) user := services.UseUserCtx(r.Context())
return ctrl.repo.Locations.GetAll(auth, auth.GID, q)
}
return adapters.Query(fn, http.StatusOK) 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 // HandleLocationCreate godoc
@ -55,16 +82,26 @@ func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc {
// @Success 200 {object} repo.LocationSummary // @Success 200 {object} repo.LocationSummary
// @Router /v1/locations [POST] // @Router /v1/locations [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationCreate() server.HandlerFunc {
fn := func(r *http.Request, createData repo.LocationCreate) (repo.LocationOut, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) createData := repo.LocationCreate{}
return ctrl.repo.Locations.Create(auth, auth.GID, createData) if err := server.Decode(r, &createData); err != nil {
} log.Err(err).Msg("failed to decode location create data")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return adapters.Action(fn, http.StatusCreated) 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 godoc // HandleLocationDelete godocs
// //
// @Summary Delete Location // @Summary Delete Location
// @Tags Locations // @Tags Locations
@ -73,17 +110,11 @@ func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/locations/{id} [DELETE] // @Router /v1/locations/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationDelete() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) { return ctrl.handleLocationGeneral()
auth := services.NewContext(r.Context())
err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GID, ID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
} }
// HandleLocationGet godoc // HandleLocationGet godocs
// //
// @Summary Get Location // @Summary Get Location
// @Tags Locations // @Tags Locations
@ -92,16 +123,11 @@ func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
// @Success 200 {object} repo.LocationOut // @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET] // @Router /v1/locations/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationGet() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) { return ctrl.handleLocationGeneral()
auth := services.NewContext(r.Context())
return ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID)
}
return adapters.CommandID("id", fn, http.StatusOK)
} }
// HandleLocationUpdate godoc // HandleLocationUpdate godocs
// //
// @Summary Update Location // @Summary Update Location
// @Tags Locations // @Tags Locations
@ -111,12 +137,58 @@ func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc {
// @Success 200 {object} repo.LocationOut // @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [PUT] // @Router /v1/locations/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationUpdate() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) { return ctrl.handleLocationGeneral()
auth := services.NewContext(r.Context()) }
body.ID = ID
return ctrl.repo.Locations.UpdateByGroup(auth, auth.GID, ID, body) func (ctrl *V1Controller) handleLocationGeneral() server.HandlerFunc {
} return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
return adapters.ActionID("id", fn, http.StatusOK) 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

@ -2,15 +2,16 @@ package v1
import ( import (
"net/http" "net/http"
"strconv"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/core/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/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
// HandleMaintenanceLogGet godoc // HandleMaintenanceGetLog godoc
// //
// @Summary Get Maintenance Log // @Summary Get Maintenance Log
// @Tags Maintenance // @Tags Maintenance
@ -18,13 +19,8 @@ import (
// @Success 200 {object} repo.MaintenanceLog // @Success 200 {object} repo.MaintenanceLog
// @Router /v1/items/{id}/maintenance [GET] // @Router /v1/items/{id}/maintenance [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc { func (ctrl *V1Controller) HandleMaintenanceLogGet() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) { return ctrl.handleMaintenanceLog()
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q)
}
return adapters.QueryID("id", fn, http.StatusOK)
} }
// HandleMaintenanceEntryCreate godoc // HandleMaintenanceEntryCreate godoc
@ -33,16 +29,11 @@ func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
// @Tags Maintenance // @Tags Maintenance
// @Produce json // @Produce json
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data" // @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
// @Success 201 {object} repo.MaintenanceEntry // @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance [POST] // @Router /v1/items/{id}/maintenance [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc { func (ctrl *V1Controller) HandleMaintenanceEntryCreate() server.HandlerFunc {
fn := func(r *http.Request, itemID uuid.UUID, body repo.MaintenanceEntryCreate) (repo.MaintenanceEntry, error) { return ctrl.handleMaintenanceLog()
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.Create(auth, itemID, body)
}
return adapters.ActionID("id", fn, http.StatusCreated)
} }
// HandleMaintenanceEntryDelete godoc // HandleMaintenanceEntryDelete godoc
@ -53,14 +44,8 @@ func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/items/{id}/maintenance/{entry_id} [DELETE] // @Router /v1/items/{id}/maintenance/{entry_id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc { func (ctrl *V1Controller) HandleMaintenanceEntryDelete() server.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID) (any, error) { return ctrl.handleMaintenanceLog()
auth := services.NewContext(r.Context())
err := ctrl.repo.MaintEntry.Delete(auth, entryID)
return nil, err
}
return adapters.CommandID("entry_id", fn, http.StatusNoContent)
} }
// HandleMaintenanceEntryUpdate godoc // HandleMaintenanceEntryUpdate godoc
@ -72,11 +57,81 @@ func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc {
// @Success 200 {object} repo.MaintenanceEntry // @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance/{entry_id} [PUT] // @Router /v1/items/{id}/maintenance/{entry_id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc { func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() server.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) { return ctrl.handleMaintenanceLog()
auth := services.NewContext(r.Context()) }
return ctrl.repo.MaintEntry.Update(auth, entryID, body)
} func (ctrl *V1Controller) handleMaintenanceLog() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
return adapters.ActionID("entry_id", fn, http.StatusOK) ctx := services.NewContext(r.Context())
itemID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
completed, _ := strconv.ParseBool(r.URL.Query().Get("completed"))
scheduled, _ := strconv.ParseBool(r.URL.Query().Get("scheduled"))
query := repo.MaintenanceLogQuery{
Completed: completed,
Scheduled: scheduled,
}
mlog, err := ctrl.repo.MaintEntry.GetLog(ctx, itemID, query)
if err != nil {
log.Err(err).Msg("failed to get items")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, mlog)
case http.MethodPost:
var create repo.MaintenanceEntryCreate
err := server.Decode(r, &create)
if err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
entry, err := ctrl.repo.MaintEntry.Create(ctx, itemID, create)
if err != nil {
log.Err(err).Msg("failed to create item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, entry)
case http.MethodPut:
entryID, err := ctrl.routeUUID(r, "entry_id")
if err != nil {
return err
}
var update repo.MaintenanceEntryUpdate
err = server.Decode(r, &update)
if err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
entry, err := ctrl.repo.MaintEntry.Update(ctx, entryID, update)
if err != nil {
log.Err(err).Msg("failed to update item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, entry)
case http.MethodDelete:
entryID, err := ctrl.routeUUID(r, "entry_id")
if err != nil {
return err
}
err = ctrl.repo.MaintEntry.Delete(ctx, entryID)
if err != nil {
log.Err(err).Msg("failed to delete item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
}
return nil
}
} }

View file

@ -1,6 +1,7 @@
package v1 package v1
import ( import (
"context"
"net/http" "net/http"
"github.com/containrrr/shoutrrr" "github.com/containrrr/shoutrrr"
@ -8,7 +9,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/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/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/internal/web/adapters"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
) )
// HandleGetUserNotifiers godoc // HandleGetUserNotifiers godoc
@ -16,13 +17,13 @@ import (
// @Summary Get Notifiers // @Summary Get Notifiers
// @Tags Notifiers // @Tags Notifiers
// @Produce json // @Produce json
// @Success 200 {object} []repo.NotifierOut // @Success 200 {object} server.Results{items=[]repo.NotifierOut}
// @Router /v1/notifiers [GET] // @Router /v1/notifiers [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc {
fn := func(r *http.Request, _ struct{}) ([]repo.NotifierOut, error) { fn := func(ctx context.Context, _ struct{}) ([]repo.NotifierOut, error) {
user := services.UseUserCtx(r.Context()) user := services.UseUserCtx(ctx)
return ctrl.repo.Notifiers.GetByUser(r.Context(), user.ID) return ctrl.repo.Notifiers.GetByUser(ctx, user.ID)
} }
return adapters.Query(fn, http.StatusOK) return adapters.Query(fn, http.StatusOK)
@ -37,10 +38,10 @@ func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc {
// @Success 200 {object} repo.NotifierOut // @Success 200 {object} repo.NotifierOut
// @Router /v1/notifiers [POST] // @Router /v1/notifiers [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc { func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc {
fn := func(r *http.Request, in repo.NotifierCreate) (repo.NotifierOut, error) { fn := func(ctx context.Context, in repo.NotifierCreate) (repo.NotifierOut, error) {
auth := services.NewContext(r.Context()) auth := services.NewContext(ctx)
return ctrl.repo.Notifiers.Create(auth, auth.GID, auth.UID, in) return ctrl.repo.Notifiers.Create(ctx, auth.GID, auth.UID, in)
} }
return adapters.Action(fn, http.StatusCreated) return adapters.Action(fn, http.StatusCreated)
@ -54,10 +55,10 @@ func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/notifiers/{id} [DELETE] // @Router /v1/notifiers/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc { func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID) (any, error) { fn := func(ctx context.Context, ID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context()) auth := services.NewContext(ctx)
return nil, ctrl.repo.Notifiers.Delete(auth, auth.UID, ID) return nil, ctrl.repo.Notifiers.Delete(ctx, auth.UID, ID)
} }
return adapters.CommandID("id", fn, http.StatusNoContent) return adapters.CommandID("id", fn, http.StatusNoContent)
@ -72,10 +73,10 @@ func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc {
// @Success 200 {object} repo.NotifierOut // @Success 200 {object} repo.NotifierOut
// @Router /v1/notifiers/{id} [PUT] // @Router /v1/notifiers/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc { func (ctrl *V1Controller) HandleUpdateNotifier() server.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) { fn := func(ctx context.Context, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) {
auth := services.NewContext(r.Context()) auth := services.NewContext(ctx)
return ctrl.repo.Notifiers.Update(auth, auth.UID, ID, in) return ctrl.repo.Notifiers.Update(ctx, auth.UID, ID, in)
} }
return adapters.ActionID("id", fn, http.StatusOK) return adapters.ActionID("id", fn, http.StatusOK)
@ -91,12 +92,12 @@ func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/notifiers/test [POST] // @Router /v1/notifiers/test [POST]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandlerNotifierTest() errchain.HandlerFunc { func (ctrl *V1Controller) HandlerNotifierTest() server.HandlerFunc {
type body struct { type body struct {
URL string `json:"url" validate:"required"` URL string `json:"url" validate:"required"`
} }
fn := func(r *http.Request, q body) (any, error) { fn := func(ctx context.Context, q body) (any, error) {
err := shoutrrr.Send(q.URL, "Test message from Homebox") err := shoutrrr.Send(q.URL, "Test message from Homebox")
return nil, err return nil, err
} }

View file

@ -5,10 +5,9 @@ import (
"image/png" "image/png"
"io" "io"
"net/http" "net/http"
"net/url"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/yeqown/go-qrcode/v2" "github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard" "github.com/yeqown/go-qrcode/writer/standard"
@ -27,29 +26,25 @@ var qrcodeLogo []byte
// @Success 200 {string} string "image/jpeg" // @Success 200 {string} string "image/jpeg"
// @Router /v1/qrcode [GET] // @Router /v1/qrcode [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGenerateQRCode() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGenerateQRCode() server.HandlerFunc {
type query struct { const MaxLength = 4_296 // assume alphanumeric characters only
// 4,296 characters is the maximum length of a QR code
Data string `schema:"data" validate:"required,max=4296"`
}
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
q, err := adapters.DecodeQuery[query](r) data := r.URL.Query().Get("data")
if err != nil {
return err
}
image, err := png.Decode(bytes.NewReader(qrcodeLogo)) image, err := png.Decode(bytes.NewReader(qrcodeLogo))
if err != nil { if err != nil {
panic(err) panic(err)
} }
decodedStr, err := url.QueryUnescape(q.Data) if len(data) > MaxLength {
if err != nil { return validate.NewFieldErrors(validate.FieldError{
return err Field: "data",
Error: "max length is 4,296 characters exceeded",
})
} }
qrc, err := qrcode.New(decodedStr) qrc, err := qrcode.New(data)
if err != nil { if err != nil {
return err return err
} }

View file

@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
) )
// HandleBillOfMaterialsExport godoc // HandleBillOfMaterialsExport godoc
@ -15,7 +15,7 @@ import (
// @Success 200 {string} string "text/csv" // @Success 200 {string} string "text/csv"
// @Router /v1/reporting/bill-of-materials [GET] // @Router /v1/reporting/bill-of-materials [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleBillOfMaterialsExport() errchain.HandlerFunc { func (ctrl *V1Controller) HandleBillOfMaterialsExport() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
actor := services.UseUserCtx(r.Context()) actor := services.UseUserCtx(r.Context())

View file

@ -5,14 +5,11 @@ import (
"time" "time"
"github.com/hay-kot/homebox/backend/internal/core/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/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
) )
// HandleGroupStatisticsLocations godoc // HandleGroupGet godoc
// //
// @Summary Get Location Statistics // @Summary Get Location Statistics
// @Tags Statistics // @Tags Statistics
@ -20,13 +17,17 @@ import (
// @Success 200 {object} []repo.TotalsByOrganizer // @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/locations [GET] // @Router /v1/groups/statistics/locations [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatisticsLocations() server.HandlerFunc {
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK) stats, err := ctrl.repo.Groups.StatsLocationsByPurchasePrice(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
}
} }
// HandleGroupStatisticsLabels godoc // HandleGroupStatisticsLabels godoc
@ -37,13 +38,17 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc
// @Success 200 {object} []repo.TotalsByOrganizer // @Success 200 {object} []repo.TotalsByOrganizer
// @Router /v1/groups/statistics/labels [GET] // @Router /v1/groups/statistics/labels [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatisticsLabels() server.HandlerFunc {
fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK) stats, err := ctrl.repo.Groups.StatsLabelsByPurchasePrice(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
}
} }
// HandleGroupStatistics godoc // HandleGroupStatistics godoc
@ -54,13 +59,17 @@ func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc {
// @Success 200 {object} repo.GroupStatistics // @Success 200 {object} repo.GroupStatistics
// @Router /v1/groups/statistics [GET] // @Router /v1/groups/statistics [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc {
fn := func(r *http.Request) (repo.GroupStatistics, error) { return func(w http.ResponseWriter, r *http.Request) error {
auth := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
return ctrl.repo.Groups.StatsGroup(auth, auth.GID)
}
return adapters.Command(fn, http.StatusOK) stats, err := ctrl.repo.Groups.StatsGroup(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
}
} }
// HandleGroupStatisticsPriceOverTime godoc // HandleGroupStatisticsPriceOverTime godoc
@ -73,7 +82,7 @@ func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc {
// @Param end query string false "end date" // @Param end query string false "end date"
// @Router /v1/groups/statistics/purchase-price [GET] // @Router /v1/groups/statistics/purchase-price [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() server.HandlerFunc {
parseDate := func(datestr string, defaultDate time.Time) (time.Time, error) { parseDate := func(datestr string, defaultDate time.Time) (time.Time, error) {
if datestr == "" { if datestr == "" {
return defaultDate, nil return defaultDate, nil
@ -99,6 +108,6 @@ func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerF
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusOK, stats) return server.Respond(w, http.StatusOK, stats)
} }
} }

View file

@ -8,8 +8,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/core/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/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -21,7 +20,7 @@ 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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
regData := services.UserRegistration{} regData := services.UserRegistration{}
@ -40,7 +39,7 @@ func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }
@ -49,10 +48,10 @@ func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
// @Summary Get User Self // @Summary Get User Self
// @Tags User // @Tags User
// @Produce json // @Produce json
// @Success 200 {object} Wrapped{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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleUserSelf() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { 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)
@ -61,7 +60,7 @@ func (ctrl *V1Controller) HandleUserSelf() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusOK, Wrap(usr)) return server.Respond(w, http.StatusOK, server.Wrap(usr))
} }
} }
@ -71,10 +70,10 @@ func (ctrl *V1Controller) HandleUserSelf() errchain.HandlerFunc {
// @Tags User // @Tags User
// @Produce json // @Produce json
// @Param payload body repo.UserUpdate true "User Data" // @Param payload body repo.UserUpdate true "User Data"
// @Success 200 {object} Wrapped{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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleUserSelfUpdate() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { 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 {
@ -88,7 +87,7 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusOK, Wrap(newData)) return server.Respond(w, http.StatusOK, server.Wrap(newData))
} }
} }
@ -100,7 +99,7 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() errchain.HandlerFunc {
// @Success 204 // @Success 204
// @Router /v1/users/self [DELETE] // @Router /v1/users/self [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc { func (ctrl *V1Controller) HandleUserSelfDelete() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo { if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden) return validate.NewRequestError(nil, http.StatusForbidden)
@ -111,7 +110,7 @@ func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }
@ -130,7 +129,7 @@ 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() errchain.HandlerFunc { func (ctrl *V1Controller) HandleUserSelfChangePassword() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error { return func(w http.ResponseWriter, r *http.Request) error {
if ctrl.isDemo { if ctrl.isDemo {
return validate.NewRequestError(nil, http.StatusForbidden) return validate.NewRequestError(nil, http.StatusForbidden)
@ -149,6 +148,6 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc {
return validate.NewRequestError(err, http.StatusInternalServerError) return validate.NewRequestError(err, http.StatusInternalServerError)
} }
return server.JSON(w, http.StatusNoContent, nil) return server.Respond(w, http.StatusNoContent, nil)
} }
} }

View file

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

View file

@ -1,9 +1,7 @@
package main package main
import ( import (
"bytes"
"context" "context"
"fmt"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -11,24 +9,16 @@ import (
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/go-chi/chi/v5" "github.com/hay-kot/homebox/backend/app/api/static/docs"
"github.com/go-chi/chi/v5/middleware"
"github.com/hay-kot/homebox/backend/internal/core/currencies"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/migrations" "github.com/hay-kot/homebox/backend/internal/data/migrations"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config" "github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid" "github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/hay-kot/httpkit/graceful" _ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
_ "github.com/hay-kot/homebox/backend/pkgs/cgofreesqlite"
) )
var ( var (
@ -37,32 +27,24 @@ var (
buildTime = "now" buildTime = "now"
) )
func build() string {
short := commit
if len(short) > 7 {
short = short[:7]
}
return fmt.Sprintf("%s, commit %s, built at %s", version, short, buildTime)
}
// @title Homebox API // @title Homebox API
// @version 1.0 // @version 1.0
// @description Track, Manage, and Organize your Things. // @description Track, Manage, and Organize your Shit.
// @contact.name Don't // @contact.name Don't
// @license.name MIT
// @BasePath /api // @BasePath /api
// @securityDefinitions.apikey Bearer // @securityDefinitions.apikey Bearer
// @in header // @in header
// @name Authorization // @name Authorization
// @description "Type 'Bearer TOKEN' to correctly set the API Key" // @description "Type 'Bearer TOKEN' to correctly set the API Key"
func main() { func main() {
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack cfg, err := config.New()
cfg, err := config.New(build(), "Homebox inventory management system")
if err != nil { if err != nil {
panic(err) panic(err)
} }
docs.SwaggerInfo.Host = cfg.Swagger.Host
if err := run(cfg); err != nil { if err := run(cfg); err != nil {
panic(err) panic(err)
} }
@ -80,12 +62,12 @@ func run(cfg *config.Config) error {
log.Fatal().Err(err).Msg("failed to create data directory") log.Fatal().Err(err).Msg("failed to create data directory")
} }
c, err := ent.Open("sqlite3", cfg.Storage.SqliteURL) c, err := ent.Open("sqlite3", cfg.Storage.SqliteUrl)
if err != nil { if err != nil {
log.Fatal(). log.Fatal().
Err(err). Err(err).
Str("driver", "sqlite"). Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL). Str("url", cfg.Storage.SqliteUrl).
Msg("failed opening connection to sqlite") Msg("failed opening connection to sqlite")
} }
defer func(c *ent.Client) { defer func(c *ent.Client) {
@ -118,7 +100,7 @@ func run(cfg *config.Config) error {
log.Fatal(). log.Fatal().
Err(err). Err(err).
Str("driver", "sqlite"). Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL). Str("url", cfg.Storage.SqliteUrl).
Msg("failed creating schema resources") Msg("failed creating schema resources")
} }
@ -128,145 +110,70 @@ func run(cfg *config.Config) error {
return err return err
} }
collectFuncs := []currencies.CollectorFunc{
currencies.CollectDefaults(),
}
if cfg.Options.CurrencyConfig != "" {
log.Info().
Str("path", cfg.Options.CurrencyConfig).
Msg("loading currency config file")
content, err := os.ReadFile(cfg.Options.CurrencyConfig)
if err != nil {
log.Fatal().
Err(err).
Str("path", cfg.Options.CurrencyConfig).
Msg("failed to read currency config file")
}
collectFuncs = append(collectFuncs, currencies.CollectJSON(bytes.NewReader(content)))
}
currencies, err := currencies.CollectionCurrencies(collectFuncs...)
if err != nil {
log.Fatal().
Err(err).
Msg("failed to collect currencies")
}
app.bus = eventbus.New()
app.db = c app.db = c
app.repos = repo.New(c, app.bus, cfg.Storage.Data) app.repos = repo.New(c, cfg.Storage.Data)
app.services = services.New( app.services = services.New(
app.repos, app.repos,
services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID), services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID),
services.WithCurrencies(currencies),
) )
// ========================================================================= // =========================================================================
// Start Server // Start Server\
logger := log.With().Caller().Logger() logger := log.With().Caller().Logger()
router := chi.NewMux() mwLogger := mid.Logger(logger)
router.Use( if app.conf.Mode == config.ModeDevelopment {
middleware.RequestID, mwLogger = mid.SugarLogger(logger)
middleware.RealIP, }
mid.Logger(logger),
middleware.Recoverer, app.server = server.NewServer(
middleware.StripSlashes, server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port),
server.WithMiddleware(
mwLogger,
mid.Errors(logger),
mid.Panic(app.conf.Mode == config.ModeDevelopment),
),
) )
chain := errchain.New(mid.Errors(logger)) app.mountRoutes(app.repos)
app.mountRoutes(router, chain, app.repos) log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
runner := graceful.NewRunner()
runner.AddFunc("server", func(ctx context.Context) error {
httpserver := http.Server{
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Web.Port),
Handler: router,
ReadTimeout: cfg.Web.ReadTimeout,
WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
}
go func() {
<-ctx.Done()
_ = httpserver.Shutdown(context.Background())
}()
log.Info().Msgf("Server is running on %s:%s", cfg.Web.Host, cfg.Web.Port)
return httpserver.ListenAndServe()
})
// ========================================================================= // =========================================================================
// Start Reoccurring Tasks // Start Reoccurring Tasks
runner.AddFunc("eventbus", app.bus.Run) go app.startBgTask(time.Duration(24)*time.Hour, func() {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background())
runner.AddFunc("seed_database", func(ctx context.Context) error {
// TODO: Remove through external API that does setup
if cfg.Demo {
log.Info().Msg("Running in demo mode, creating demo data")
app.SetupDemo()
}
return nil
})
runner.AddPlugin(NewTask("purge-tokens", time.Duration(24)*time.Hour, func(ctx context.Context) {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(ctx)
if err != nil { if err != nil {
log.Error(). log.Error().
Err(err). Err(err).
Msg("failed to purge expired tokens") Msg("failed to purge expired tokens")
} }
})) })
go app.startBgTask(time.Duration(24)*time.Hour, func() {
runner.AddPlugin(NewTask("purge-invitations", time.Duration(24)*time.Hour, func(ctx context.Context) { _, err := app.repos.Groups.InvitationPurge(context.Background())
_, err := app.repos.Groups.InvitationPurge(ctx)
if err != nil { if err != nil {
log.Error(). log.Error().
Err(err). Err(err).
Msg("failed to purge expired invitations") Msg("failed to purge expired invitations")
} }
})) })
runner.AddPlugin(NewTask("send-notifications", time.Duration(1)*time.Hour, func(ctx context.Context) { // TODO: Remove through external API that does setup
now := time.Now() if cfg.Demo {
log.Info().Msg("Running in demo mode, creating demo data")
if now.Hour() == 8 { app.SetupDemo()
fmt.Println("run notifiers")
err := app.services.BackgroundService.SendNotifiersToday(context.Background())
if err != nil {
log.Error().
Err(err).
Msg("failed to send notifiers")
}
}
}))
if cfg.Debug.Enabled {
runner.AddFunc("debug", func(ctx context.Context) error {
debugserver := http.Server{
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Debug.Port),
Handler: app.debugRouter(),
ReadTimeout: cfg.Web.ReadTimeout,
WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
}
go func() {
<-ctx.Done()
_ = debugserver.Shutdown(context.Background())
}()
log.Info().Msgf("Debug server is running on %s:%s", cfg.Web.Host, cfg.Debug.Port)
return debugserver.ListenAndServe()
})
} }
return runner.Start(context.Background()) 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

@ -7,11 +7,9 @@ import (
"net/url" "net/url"
"strings" "strings"
v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1"
"github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
) )
type tokenHasKey struct { type tokenHasKey struct {
@ -32,9 +30,9 @@ const (
// the required roles, a 403 Forbidden will be returned. // the required roles, a 403 Forbidden will be returned.
// //
// WARNING: This middleware _MUST_ be called after mwAuthToken or else it will panic // WARNING: This middleware _MUST_ be called after mwAuthToken or else it will panic
func (a *app) mwRoles(rm RoleMode, required ...string) errchain.Middleware { func (a *app) mwRoles(rm RoleMode, required ...string) server.Middleware {
return func(next errchain.Handler) errchain.Handler { return func(next server.Handler) server.Handler {
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context() ctx := r.Context()
maybeToken := ctx.Value(hashedToken) maybeToken := ctx.Value(hashedToken)
@ -96,6 +94,20 @@ func getQuery(r *http.Request) (string, error) {
return token, nil return token, nil
} }
func getCookie(r *http.Request) (string, error) {
cookie, err := r.Cookie("hb.auth.token")
if err != nil {
return "", errors.New("access_token cookie is required")
}
token, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", errors.New("access_token cookie is required")
}
return token, nil
}
// mwAuthToken is a middleware that will check the database for a stateful token // mwAuthToken is a middleware that will check the database for a stateful token
// and attach it's user to the request context, or return an appropriate error. // and attach it's user to the request context, or return an appropriate error.
// Authorization support is by token via Headers or Query Parameter // Authorization support is by token via Headers or Query Parameter
@ -103,35 +115,26 @@ func getQuery(r *http.Request) (string, error) {
// Example: // Example:
// - header = "Bearer 1234567890" // - header = "Bearer 1234567890"
// - query = "?access_token=1234567890" // - query = "?access_token=1234567890"
func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler { // - cookie = hb.auth.token = 1234567890
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { func (a *app) mwAuthToken(next server.Handler) server.Handler {
return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
keyFuncs := [...]KeyFunc{
getBearer,
getCookie,
getQuery,
}
var requestToken string var requestToken string
for _, keyFunc := range keyFuncs {
// We ignore the error to allow the next strategy to be attempted token, err := keyFunc(r)
{ if err == nil {
cookies, _ := v1.GetCookies(r) requestToken = token
if cookies != nil { break
requestToken = cookies.Token
} }
} }
if requestToken == "" { if requestToken == "" {
keyFuncs := [...]KeyFunc{ return validate.NewRequestError(errors.New("Authorization header or query is required"), http.StatusUnauthorized)
getBearer,
getQuery,
}
for _, keyFunc := range keyFuncs {
token, err := keyFunc(r)
if err == nil {
requestToken = token
break
}
}
}
if requestToken == "" {
return validate.NewRequestError(errors.New("authorization header or query is required"), http.StatusUnauthorized)
} }
requestToken = strings.TrimPrefix(requestToken, "Bearer ") requestToken = strings.TrimPrefix(requestToken, "Bearer ")
@ -141,11 +144,7 @@ func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler {
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 {
if ent.IsNotFound(err) { return validate.NewRequestError(errors.New("valid authorization header is required"), http.StatusUnauthorized)
return validate.NewRequestError(errors.New("valid authorization token is required"), http.StatusUnauthorized)
}
return err
} }
r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken)) r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken))

View file

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

View file

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

View file

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

View file

@ -3,21 +3,20 @@ package main
import ( import (
"embed" "embed"
"errors" "errors"
"fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
"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/handlers/debughandlers"
v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1" v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1"
"github.com/hay-kot/homebox/backend/app/api/providers"
_ "github.com/hay-kot/homebox/backend/app/api/static/docs" _ "github.com/hay-kot/homebox/backend/app/api/static/docs"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/homebox/backend/pkgs/server"
httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
) )
const prefix = "/api" const prefix = "/api"
@ -37,133 +36,119 @@ func (a *app) debugRouter() *http.ServeMux {
} }
// registerRoutes registers all the routes for the API // registerRoutes registers all the routes for the API
func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllRepos) { func (a *app) mountRoutes(repos *repo.AllRepos) {
registerMimes() registerMimes()
r.Get("/swagger/*", httpSwagger.Handler( a.server.Get("/swagger/*", server.ToHandler(httpSwagger.Handler(
httpSwagger.URL("/swagger/doc.json"), 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( v1Ctrl := v1.NewControllerV1(
a.services, a.services,
a.repos, a.repos,
a.bus,
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize), v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
v1.WithRegistration(a.conf.Options.AllowRegistration), v1.WithRegistration(a.conf.Options.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"), chain.ToHandlerFunc(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.Get(v1Base("/currencies"), chain.ToHandlerFunc(v1Ctrl.HandleCurrency())) a.server.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
a.server.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
providers := []v1.AuthProvider{ userMW := []server.Middleware{
providers.NewLocalProvider(a.services.User),
}
r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
userMW := []errchain.Middleware{
a.mwAuthToken, a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String()), a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
} }
r.Get(v1Base("/ws/events"), chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...)) a.server.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf(), userMW...)
r.Get(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...)) a.server.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate(), userMW...)
r.Put(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...)) a.server.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete(), userMW...)
r.Delete(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...)) a.server.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout(), userMW...)
r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...)) a.server.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh(), userMW...)
r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...)) a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), userMW...)
r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...)) a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), userMW...)
r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...)) a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), userMW...)
r.Get(v1Base("/groups/statistics/purchase-price"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...)) a.server.Get(v1Base("/groups/statistics/purchase-price"), v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...)
r.Get(v1Base("/groups/statistics/locations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...)) a.server.Get(v1Base("/groups/statistics/locations"), v1Ctrl.HandleGroupStatisticsLocations(), userMW...)
r.Get(v1Base("/groups/statistics/labels"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...)) a.server.Get(v1Base("/groups/statistics/labels"), v1Ctrl.HandleGroupStatisticsLabels(), userMW...)
// 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"), chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...)) a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), userMW...)
r.Put(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...)) a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), userMW...)
r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...)) a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), userMW...)
r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...)) a.server.Post(v1Base("/actions/zero-item-time-fields"), v1Ctrl.HandleItemDateZeroOut(), userMW...)
r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...)) a.server.Post(v1Base("/actions/ensure-import-refs"), v1Ctrl.HandleEnsureImportRefs(), userMW...)
r.Post(v1Base("/actions/set-primary-photos"), chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...)) a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), userMW...)
r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...)) a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), userMW...)
r.Get(v1Base("/locations/tree"), chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...)) a.server.Get(v1Base("/locations/tree"), v1Ctrl.HandleLocationTreeQuery(), userMW...)
r.Get(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...)) a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), userMW...)
r.Put(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...)) a.server.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate(), userMW...)
r.Delete(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...)) a.server.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete(), userMW...)
r.Get(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...)) a.server.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll(), userMW...)
r.Post(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...)) a.server.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate(), userMW...)
r.Get(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...)) a.server.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet(), userMW...)
r.Put(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...)) a.server.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate(), userMW...)
r.Delete(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...)) a.server.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete(), userMW...)
r.Get(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...)) a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), userMW...)
r.Post(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...)) a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), userMW...)
r.Post(v1Base("/items/import"), chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...)) a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), userMW...)
r.Get(v1Base("/items/export"), chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...)) a.server.Get(v1Base("/items/export"), v1Ctrl.HandleItemsExport(), userMW...)
r.Get(v1Base("/items/fields"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...)) a.server.Get(v1Base("/items/fields"), v1Ctrl.HandleGetAllCustomFieldNames(), userMW...)
r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...)) a.server.Get(v1Base("/items/fields/values"), v1Ctrl.HandleGetAllCustomFieldValues(), userMW...)
r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...)) a.server.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet(), userMW...)
r.Get(v1Base("/items/{id}/path"), chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...)) a.server.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate(), userMW...)
r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...)) a.server.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete(), userMW...)
r.Patch(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...))
r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
r.Post(v1Base("/items/{id}/attachments"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...)) a.server.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate(), userMW...)
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...)) a.server.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate(), userMW...)
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...)) a.server.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete(), userMW...)
r.Get(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...)) a.server.Get(v1Base("/items/{id}/maintenance"), v1Ctrl.HandleMaintenanceEntryCreate(), userMW...)
r.Post(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...)) a.server.Post(v1Base("/items/{id}/maintenance"), v1Ctrl.HandleMaintenanceEntryCreate(), userMW...)
r.Put(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...)) a.server.Put(v1Base("/items/{id}/maintenance/{entry_id}"), v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...)
r.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...)) a.server.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), v1Ctrl.HandleMaintenanceEntryDelete(), userMW...)
r.Get(v1Base("/assets/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...)) a.server.Get(v1Base("/asset/{id}"), v1Ctrl.HandleAssetGet(), userMW...)
// Notifiers // Notifiers
r.Get(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...)) a.server.Get(v1Base("/notifiers"), v1Ctrl.HandleGetUserNotifiers(), userMW...)
r.Post(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...)) a.server.Post(v1Base("/notifiers"), v1Ctrl.HandleCreateNotifier(), userMW...)
r.Put(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...)) a.server.Put(v1Base("/notifiers/{id}"), v1Ctrl.HandleUpdateNotifier(), userMW...)
r.Delete(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...)) a.server.Delete(v1Base("/notifiers/{id}"), v1Ctrl.HandleDeleteNotifier(), userMW...)
r.Post(v1Base("/notifiers/test"), chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...)) a.server.Post(v1Base("/notifiers/test"), v1Ctrl.HandlerNotifierTest(), userMW...)
// Asset-Like endpoints // Asset-Like endpoints
assetMW := []errchain.Middleware{ a.server.Get(
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
}
r.Get(
v1Base("/qrcode"), v1Base("/qrcode"),
chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...), v1Ctrl.HandleGenerateQRCode(),
a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
) )
r.Get( a.server.Get(
v1Base("/items/{id}/attachments/{attachment_id}"), v1Base("/items/{id}/attachments/{attachment_id}"),
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...), v1Ctrl.HandleItemAttachmentGet(),
a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
) )
// Reporting Services // Reporting Services
r.Get(v1Base("/reporting/bill-of-materials"), chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...)) a.server.Get(v1Base("/reporting/bill-of-materials"), v1Ctrl.HandleBillOfMaterialsExport(), userMW...)
r.NotFound(chain.ToHandlerFunc(notFoundHandler())) a.server.NotFound(notFoundHandler())
} }
func registerMimes() { func registerMimes() {
@ -180,13 +165,13 @@ 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() errchain.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 {
return err return err
} }
defer func() { _ = f.Close() }() defer f.Close()
stat, _ := f.Stat() stat, _ := f.Stat()
if stat.IsDir() { if stat.IsDir() {

View file

@ -1,4 +1,5 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT // Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs package docs
import "github.com/swaggo/swag" import "github.com/swaggo/swag"
@ -12,6 +13,9 @@ const docTemplate = `{
"contact": { "contact": {
"name": "Don't" "name": "Don't"
}, },
"license": {
"name": "MIT"
},
"version": "{{.Version}}" "version": "{{.Version}}"
}, },
"host": "{{.Host}}", "host": "{{.Host}}",
@ -67,31 +71,6 @@ const docTemplate = `{
} }
} }
}, },
"/v1/actions/set-primary-photos": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Sets the first photo of each item as the primary photo",
"produces": [
"application/json"
],
"tags": [
"Actions"
],
"summary": "Set Primary Photos",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.ActionAmountResult"
}
}
}
}
},
"/v1/actions/zero-item-time-fields": { "/v1/actions/zero-item-time-fields": {
"post": { "post": {
"security": [ "security": [
@ -150,25 +129,6 @@ const docTemplate = `{
} }
} }
}, },
"/v1/currency": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Base"
],
"summary": "Currency",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/currencies.Currency"
}
}
}
}
},
"/v1/groups": { "/v1/groups": {
"get": { "get": {
"security": [ "security": [
@ -429,16 +389,6 @@ const docTemplate = `{
"description": "location Ids", "description": "location Ids",
"name": "locations", "name": "locations",
"in": "query" "in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "parent Ids",
"name": "parentIds",
"in": "query"
} }
], ],
"responses": { "responses": {
@ -475,8 +425,8 @@ const docTemplate = `{
} }
], ],
"responses": { "responses": {
"201": { "200": {
"description": "Created", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/repo.ItemSummary" "$ref": "#/definitions/repo.ItemSummary"
} }
@ -688,46 +638,6 @@ const docTemplate = `{
"description": "No Content" "description": "No Content"
} }
} }
},
"patch": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Update Item",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Item Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.ItemPatch"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.ItemOut"
}
}
}
} }
}, },
"/v1/items/{id}/attachments": { "/v1/items/{id}/attachments": {
@ -784,7 +694,7 @@ const docTemplate = `{
"422": { "422": {
"description": "Unprocessable Entity", "description": "Unprocessable Entity",
"schema": { "schema": {
"$ref": "#/definitions/validate.ErrorResponse" "$ref": "#/definitions/server.ErrorResponse"
} }
} }
} }
@ -954,8 +864,8 @@ const docTemplate = `{
} }
], ],
"responses": { "responses": {
"201": { "200": {
"description": "Created", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/repo.MaintenanceEntry" "$ref": "#/definitions/repo.MaintenanceEntry"
} }
@ -1017,42 +927,6 @@ const docTemplate = `{
} }
} }
}, },
"/v1/items/{id}/path": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get the full path of an item",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemPath"
}
}
}
}
}
},
"/v1/labels": { "/v1/labels": {
"get": { "get": {
"security": [ "security": [
@ -1071,10 +945,22 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.LabelOut" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LabelOut"
}
}
}
}
]
} }
} }
} }
@ -1231,10 +1117,22 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.LocationOutCount" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LocationOutCount"
}
}
}
}
]
} }
} }
} }
@ -1299,10 +1197,22 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.TreeItem" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.TreeItem"
}
}
}
}
]
} }
} }
} }
@ -1427,10 +1337,22 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.NotifierOut" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.NotifierOut"
}
}
}
}
]
} }
} }
} }
@ -1639,7 +1561,7 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/v1.APISummary" "$ref": "#/definitions/v1.ApiSummary"
} }
} }
} }
@ -1701,21 +1623,6 @@ const docTemplate = `{
"description": "string", "description": "string",
"name": "password", "name": "password",
"in": "formData" "in": "formData"
},
{
"description": "Login Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.LoginForm"
}
},
{
"type": "string",
"description": "auth provider",
"name": "provider",
"in": "query"
} }
], ],
"responses": { "responses": {
@ -1812,7 +1719,7 @@ const docTemplate = `{
"schema": { "schema": {
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/v1.Wrapped" "$ref": "#/definitions/server.Result"
}, },
{ {
"type": "object", "type": "object",
@ -1857,7 +1764,7 @@ const docTemplate = `{
"schema": { "schema": {
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/v1.Wrapped" "$ref": "#/definitions/server.Result"
}, },
{ {
"type": "object", "type": "object",
@ -1894,23 +1801,6 @@ const docTemplate = `{
} }
}, },
"definitions": { "definitions": {
"currencies.Currency": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"local": {
"type": "string"
},
"name": {
"type": "string"
},
"symbol": {
"type": "string"
}
}
},
"repo.DocumentOut": { "repo.DocumentOut": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1991,9 +1881,6 @@ const docTemplate = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"primary": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -2005,9 +1892,6 @@ const docTemplate = `{
"repo.ItemAttachmentUpdate": { "repo.ItemAttachmentUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
"primary": {
"type": "boolean"
},
"title": { "title": {
"type": "string" "type": "string"
}, },
@ -2018,13 +1902,9 @@ const docTemplate = `{
}, },
"repo.ItemCreate": { "repo.ItemCreate": {
"type": "object", "type": "object",
"required": [
"name"
],
"properties": { "properties": {
"description": { "description": {
"type": "string", "type": "string"
"maxLength": 1000
}, },
"labelIds": { "labelIds": {
"type": "array", "type": "array",
@ -2037,9 +1917,7 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"name": { "name": {
"type": "string", "type": "string"
"maxLength": 255,
"minLength": 1
}, },
"parentId": { "parentId": {
"type": "string", "type": "string",
@ -2086,6 +1964,12 @@ const docTemplate = `{
"$ref": "#/definitions/repo.ItemAttachment" "$ref": "#/definitions/repo.ItemAttachment"
} }
}, },
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@ -2101,9 +1985,6 @@ const docTemplate = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"imageId": {
"type": "string"
},
"insured": { "insured": {
"type": "boolean" "type": "boolean"
}, },
@ -2119,13 +2000,9 @@ const docTemplate = `{
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"allOf": [
{
"$ref": "#/definitions/repo.LocationSummary"
}
],
"x-nullable": true, "x-nullable": true,
"x-omitempty": true "x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
}, },
"manufacturer": { "manufacturer": {
"type": "string" "type": "string"
@ -2141,13 +2018,9 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"parent": { "parent": {
"allOf": [
{
"$ref": "#/definitions/repo.ItemSummary"
}
],
"x-nullable": true, "x-nullable": true,
"x-omitempty": true "x-omitempty": true,
"$ref": "#/definitions/repo.ItemSummary"
}, },
"purchaseFrom": { "purchaseFrom": {
"type": "string" "type": "string"
@ -2191,33 +2064,6 @@ const docTemplate = `{
} }
} }
}, },
"repo.ItemPatch": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"quantity": {
"type": "integer",
"x-nullable": true,
"x-omitempty": true
}
}
},
"repo.ItemPath": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"$ref": "#/definitions/repo.ItemType"
}
}
},
"repo.ItemSummary": { "repo.ItemSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2233,9 +2079,6 @@ const docTemplate = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"imageId": {
"type": "string"
},
"insured": { "insured": {
"type": "boolean" "type": "boolean"
}, },
@ -2247,13 +2090,9 @@ const docTemplate = `{
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"allOf": [
{
"$ref": "#/definitions/repo.LocationSummary"
}
],
"x-nullable": true, "x-nullable": true,
"x-omitempty": true "x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
}, },
"name": { "name": {
"type": "string" "type": "string"
@ -2270,17 +2109,6 @@ const docTemplate = `{
} }
} }
}, },
"repo.ItemType": {
"type": "string",
"enum": [
"location",
"item"
],
"x-enum-varnames": [
"ItemTypeLocation",
"ItemTypeItem"
]
},
"repo.ItemUpdate": { "repo.ItemUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2373,27 +2201,22 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"warrantyExpires": { "warrantyExpires": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
}, },
"repo.LabelCreate": { "repo.LabelCreate": {
"type": "object", "type": "object",
"required": [
"name"
],
"properties": { "properties": {
"color": { "color": {
"type": "string" "type": "string"
}, },
"description": { "description": {
"type": "string", "type": "string"
"maxLength": 255
}, },
"name": { "name": {
"type": "string", "type": "string"
"maxLength": 255,
"minLength": 1
} }
} }
}, },
@ -2409,6 +2232,12 @@ const docTemplate = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -2470,6 +2299,12 @@ const docTemplate = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -2546,6 +2381,7 @@ const docTemplate = `{
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": { "completedDate": {
"description": "Sold",
"type": "string" "type": "string"
}, },
"cost": { "cost": {
@ -2562,17 +2398,16 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"scheduledDate": { "scheduledDate": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
}, },
"repo.MaintenanceEntryCreate": { "repo.MaintenanceEntryCreate": {
"type": "object", "type": "object",
"required": [
"name"
],
"properties": { "properties": {
"completedDate": { "completedDate": {
"description": "Sold",
"type": "string" "type": "string"
}, },
"cost": { "cost": {
@ -2586,6 +2421,7 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"scheduledDate": { "scheduledDate": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
@ -2594,6 +2430,7 @@ const docTemplate = `{
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": { "completedDate": {
"description": "Sold",
"type": "string" "type": "string"
}, },
"cost": { "cost": {
@ -2607,6 +2444,7 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"scheduledDate": { "scheduledDate": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
@ -2825,6 +2663,39 @@ const docTemplate = `{
} }
} }
}, },
"server.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"server.Result": {
"type": "object",
"properties": {
"details": {},
"error": {
"type": "boolean"
},
"item": {},
"message": {
"type": "string"
}
}
},
"server.Results": {
"type": "object",
"properties": {
"items": {}
}
},
"services.UserRegistration": { "services.UserRegistration": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2842,7 +2713,15 @@ const docTemplate = `{
} }
} }
}, },
"v1.APISummary": { "v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.ApiSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
"allowRegistration": { "allowRegistration": {
@ -2871,14 +2750,6 @@ const docTemplate = `{
} }
} }
}, },
"v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.Build": { "v1.Build": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2920,17 +2791,12 @@ const docTemplate = `{
}, },
"v1.GroupInvitationCreate": { "v1.GroupInvitationCreate": {
"type": "object", "type": "object",
"required": [
"uses"
],
"properties": { "properties": {
"expiresAt": { "expiresAt": {
"type": "string" "type": "string"
}, },
"uses": { "uses": {
"type": "integer", "type": "integer"
"maximum": 100,
"minimum": 1
} }
} }
}, },
@ -2942,20 +2808,6 @@ const docTemplate = `{
} }
} }
}, },
"v1.LoginForm": {
"type": "object",
"properties": {
"password": {
"type": "string"
},
"stayLoggedIn": {
"type": "boolean"
},
"username": {
"type": "string"
}
}
},
"v1.TokenResponse": { "v1.TokenResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2969,23 +2821,6 @@ const docTemplate = `{
"type": "string" "type": "string"
} }
} }
},
"v1.Wrapped": {
"type": "object",
"properties": {
"item": {}
}
},
"validate.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "string"
}
}
} }
}, },
"securityDefinitions": { "securityDefinitions": {
@ -3005,11 +2840,9 @@ var SwaggerInfo = &swag.Spec{
BasePath: "/api", BasePath: "/api",
Schemes: []string{}, Schemes: []string{},
Title: "Homebox API", Title: "Homebox API",
Description: "Track, Manage, and Organize your Things.", Description: "Track, Manage, and Organize your Shit.",
InfoInstanceName: "swagger", InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate, SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
} }
func init() { func init() {

View file

@ -1,11 +1,14 @@
{ {
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "Track, Manage, and Organize your Things.", "description": "Track, Manage, and Organize your Shit.",
"title": "Homebox API", "title": "Homebox API",
"contact": { "contact": {
"name": "Don't" "name": "Don't"
}, },
"license": {
"name": "MIT"
},
"version": "1.0" "version": "1.0"
}, },
"basePath": "/api", "basePath": "/api",
@ -60,31 +63,6 @@
} }
} }
}, },
"/v1/actions/set-primary-photos": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Sets the first photo of each item as the primary photo",
"produces": [
"application/json"
],
"tags": [
"Actions"
],
"summary": "Set Primary Photos",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.ActionAmountResult"
}
}
}
}
},
"/v1/actions/zero-item-time-fields": { "/v1/actions/zero-item-time-fields": {
"post": { "post": {
"security": [ "security": [
@ -143,25 +121,6 @@
} }
} }
}, },
"/v1/currency": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Base"
],
"summary": "Currency",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/currencies.Currency"
}
}
}
}
},
"/v1/groups": { "/v1/groups": {
"get": { "get": {
"security": [ "security": [
@ -422,16 +381,6 @@
"description": "location Ids", "description": "location Ids",
"name": "locations", "name": "locations",
"in": "query" "in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "parent Ids",
"name": "parentIds",
"in": "query"
} }
], ],
"responses": { "responses": {
@ -468,8 +417,8 @@
} }
], ],
"responses": { "responses": {
"201": { "200": {
"description": "Created", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/repo.ItemSummary" "$ref": "#/definitions/repo.ItemSummary"
} }
@ -681,46 +630,6 @@
"description": "No Content" "description": "No Content"
} }
} }
},
"patch": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Update Item",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Item Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.ItemPatch"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.ItemOut"
}
}
}
} }
}, },
"/v1/items/{id}/attachments": { "/v1/items/{id}/attachments": {
@ -777,7 +686,7 @@
"422": { "422": {
"description": "Unprocessable Entity", "description": "Unprocessable Entity",
"schema": { "schema": {
"$ref": "#/definitions/validate.ErrorResponse" "$ref": "#/definitions/server.ErrorResponse"
} }
} }
} }
@ -947,8 +856,8 @@
} }
], ],
"responses": { "responses": {
"201": { "200": {
"description": "Created", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/repo.MaintenanceEntry" "$ref": "#/definitions/repo.MaintenanceEntry"
} }
@ -1010,42 +919,6 @@
} }
} }
}, },
"/v1/items/{id}/path": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Items"
],
"summary": "Get the full path of an item",
"parameters": [
{
"type": "string",
"description": "Item ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemPath"
}
}
}
}
}
},
"/v1/labels": { "/v1/labels": {
"get": { "get": {
"security": [ "security": [
@ -1064,10 +937,22 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.LabelOut" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LabelOut"
}
}
}
}
]
} }
} }
} }
@ -1224,10 +1109,22 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.LocationOutCount" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LocationOutCount"
}
}
}
}
]
} }
} }
} }
@ -1292,10 +1189,22 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.TreeItem" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.TreeItem"
}
}
}
}
]
} }
} }
} }
@ -1420,10 +1329,22 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"type": "array", "allOf": [
"items": { {
"$ref": "#/definitions/repo.NotifierOut" "$ref": "#/definitions/server.Results"
} },
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.NotifierOut"
}
}
}
}
]
} }
} }
} }
@ -1632,7 +1553,7 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/v1.APISummary" "$ref": "#/definitions/v1.ApiSummary"
} }
} }
} }
@ -1694,21 +1615,6 @@
"description": "string", "description": "string",
"name": "password", "name": "password",
"in": "formData" "in": "formData"
},
{
"description": "Login Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.LoginForm"
}
},
{
"type": "string",
"description": "auth provider",
"name": "provider",
"in": "query"
} }
], ],
"responses": { "responses": {
@ -1805,7 +1711,7 @@
"schema": { "schema": {
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/v1.Wrapped" "$ref": "#/definitions/server.Result"
}, },
{ {
"type": "object", "type": "object",
@ -1850,7 +1756,7 @@
"schema": { "schema": {
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/v1.Wrapped" "$ref": "#/definitions/server.Result"
}, },
{ {
"type": "object", "type": "object",
@ -1887,23 +1793,6 @@
} }
}, },
"definitions": { "definitions": {
"currencies.Currency": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"local": {
"type": "string"
},
"name": {
"type": "string"
},
"symbol": {
"type": "string"
}
}
},
"repo.DocumentOut": { "repo.DocumentOut": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1984,9 +1873,6 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"primary": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -1998,9 +1884,6 @@
"repo.ItemAttachmentUpdate": { "repo.ItemAttachmentUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
"primary": {
"type": "boolean"
},
"title": { "title": {
"type": "string" "type": "string"
}, },
@ -2011,13 +1894,9 @@
}, },
"repo.ItemCreate": { "repo.ItemCreate": {
"type": "object", "type": "object",
"required": [
"name"
],
"properties": { "properties": {
"description": { "description": {
"type": "string", "type": "string"
"maxLength": 1000
}, },
"labelIds": { "labelIds": {
"type": "array", "type": "array",
@ -2030,9 +1909,7 @@
"type": "string" "type": "string"
}, },
"name": { "name": {
"type": "string", "type": "string"
"maxLength": 255,
"minLength": 1
}, },
"parentId": { "parentId": {
"type": "string", "type": "string",
@ -2079,6 +1956,12 @@
"$ref": "#/definitions/repo.ItemAttachment" "$ref": "#/definitions/repo.ItemAttachment"
} }
}, },
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@ -2094,9 +1977,6 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"imageId": {
"type": "string"
},
"insured": { "insured": {
"type": "boolean" "type": "boolean"
}, },
@ -2112,13 +1992,9 @@
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"allOf": [
{
"$ref": "#/definitions/repo.LocationSummary"
}
],
"x-nullable": true, "x-nullable": true,
"x-omitempty": true "x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
}, },
"manufacturer": { "manufacturer": {
"type": "string" "type": "string"
@ -2134,13 +2010,9 @@
"type": "string" "type": "string"
}, },
"parent": { "parent": {
"allOf": [
{
"$ref": "#/definitions/repo.ItemSummary"
}
],
"x-nullable": true, "x-nullable": true,
"x-omitempty": true "x-omitempty": true,
"$ref": "#/definitions/repo.ItemSummary"
}, },
"purchaseFrom": { "purchaseFrom": {
"type": "string" "type": "string"
@ -2184,33 +2056,6 @@
} }
} }
}, },
"repo.ItemPatch": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"quantity": {
"type": "integer",
"x-nullable": true,
"x-omitempty": true
}
}
},
"repo.ItemPath": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"$ref": "#/definitions/repo.ItemType"
}
}
},
"repo.ItemSummary": { "repo.ItemSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2226,9 +2071,6 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"imageId": {
"type": "string"
},
"insured": { "insured": {
"type": "boolean" "type": "boolean"
}, },
@ -2240,13 +2082,9 @@
}, },
"location": { "location": {
"description": "Edges", "description": "Edges",
"allOf": [
{
"$ref": "#/definitions/repo.LocationSummary"
}
],
"x-nullable": true, "x-nullable": true,
"x-omitempty": true "x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
}, },
"name": { "name": {
"type": "string" "type": "string"
@ -2263,17 +2101,6 @@
} }
} }
}, },
"repo.ItemType": {
"type": "string",
"enum": [
"location",
"item"
],
"x-enum-varnames": [
"ItemTypeLocation",
"ItemTypeItem"
]
},
"repo.ItemUpdate": { "repo.ItemUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2366,27 +2193,22 @@
"type": "string" "type": "string"
}, },
"warrantyExpires": { "warrantyExpires": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
}, },
"repo.LabelCreate": { "repo.LabelCreate": {
"type": "object", "type": "object",
"required": [
"name"
],
"properties": { "properties": {
"color": { "color": {
"type": "string" "type": "string"
}, },
"description": { "description": {
"type": "string", "type": "string"
"maxLength": 255
}, },
"name": { "name": {
"type": "string", "type": "string"
"maxLength": 255,
"minLength": 1
} }
} }
}, },
@ -2402,6 +2224,12 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -2463,6 +2291,12 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -2539,6 +2373,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": { "completedDate": {
"description": "Sold",
"type": "string" "type": "string"
}, },
"cost": { "cost": {
@ -2555,17 +2390,16 @@
"type": "string" "type": "string"
}, },
"scheduledDate": { "scheduledDate": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
}, },
"repo.MaintenanceEntryCreate": { "repo.MaintenanceEntryCreate": {
"type": "object", "type": "object",
"required": [
"name"
],
"properties": { "properties": {
"completedDate": { "completedDate": {
"description": "Sold",
"type": "string" "type": "string"
}, },
"cost": { "cost": {
@ -2579,6 +2413,7 @@
"type": "string" "type": "string"
}, },
"scheduledDate": { "scheduledDate": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
@ -2587,6 +2422,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"completedDate": { "completedDate": {
"description": "Sold",
"type": "string" "type": "string"
}, },
"cost": { "cost": {
@ -2600,6 +2436,7 @@
"type": "string" "type": "string"
}, },
"scheduledDate": { "scheduledDate": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
@ -2818,6 +2655,39 @@
} }
} }
}, },
"server.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"server.Result": {
"type": "object",
"properties": {
"details": {},
"error": {
"type": "boolean"
},
"item": {},
"message": {
"type": "string"
}
}
},
"server.Results": {
"type": "object",
"properties": {
"items": {}
}
},
"services.UserRegistration": { "services.UserRegistration": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2835,7 +2705,15 @@
} }
} }
}, },
"v1.APISummary": { "v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.ApiSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
"allowRegistration": { "allowRegistration": {
@ -2864,14 +2742,6 @@
} }
} }
}, },
"v1.ActionAmountResult": {
"type": "object",
"properties": {
"completed": {
"type": "integer"
}
}
},
"v1.Build": { "v1.Build": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2913,17 +2783,12 @@
}, },
"v1.GroupInvitationCreate": { "v1.GroupInvitationCreate": {
"type": "object", "type": "object",
"required": [
"uses"
],
"properties": { "properties": {
"expiresAt": { "expiresAt": {
"type": "string" "type": "string"
}, },
"uses": { "uses": {
"type": "integer", "type": "integer"
"maximum": 100,
"minimum": 1
} }
} }
}, },
@ -2935,20 +2800,6 @@
} }
} }
}, },
"v1.LoginForm": {
"type": "object",
"properties": {
"password": {
"type": "string"
},
"stayLoggedIn": {
"type": "boolean"
},
"username": {
"type": "string"
}
}
},
"v1.TokenResponse": { "v1.TokenResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2962,23 +2813,6 @@
"type": "string" "type": "string"
} }
} }
},
"v1.Wrapped": {
"type": "object",
"properties": {
"item": {}
}
},
"validate.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "string"
}
}
} }
}, },
"securityDefinitions": { "securityDefinitions": {

View file

@ -1,16 +1,5 @@
basePath: /api basePath: /api
definitions: definitions:
currencies.Currency:
properties:
code:
type: string
local:
type: string
name:
type: string
symbol:
type: string
type: object
repo.DocumentOut: repo.DocumentOut:
properties: properties:
id: id:
@ -63,8 +52,6 @@ definitions:
$ref: '#/definitions/repo.DocumentOut' $ref: '#/definitions/repo.DocumentOut'
id: id:
type: string type: string
primary:
type: boolean
type: type:
type: string type: string
updatedAt: updatedAt:
@ -72,8 +59,6 @@ definitions:
type: object type: object
repo.ItemAttachmentUpdate: repo.ItemAttachmentUpdate:
properties: properties:
primary:
type: boolean
title: title:
type: string type: string
type: type:
@ -82,7 +67,6 @@ definitions:
repo.ItemCreate: repo.ItemCreate:
properties: properties:
description: description:
maxLength: 1000
type: string type: string
labelIds: labelIds:
items: items:
@ -92,14 +76,10 @@ definitions:
description: Edges description: Edges
type: string type: string
name: name:
maxLength: 255
minLength: 1
type: string type: string
parentId: parentId:
type: string type: string
x-nullable: true x-nullable: true
required:
- name
type: object type: object
repo.ItemField: repo.ItemField:
properties: properties:
@ -127,6 +107,10 @@ definitions:
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:
@ -137,8 +121,6 @@ definitions:
type: array type: array
id: id:
type: string type: string
imageId:
type: string
insured: insured:
type: boolean type: boolean
labels: labels:
@ -149,8 +131,7 @@ definitions:
description: Warranty description: Warranty
type: boolean type: boolean
location: location:
allOf: $ref: '#/definitions/repo.LocationSummary'
- $ref: '#/definitions/repo.LocationSummary'
description: Edges description: Edges
x-nullable: true x-nullable: true
x-omitempty: true x-omitempty: true
@ -164,8 +145,7 @@ definitions:
description: Extras description: Extras
type: string type: string
parent: parent:
allOf: $ref: '#/definitions/repo.ItemSummary'
- $ref: '#/definitions/repo.ItemSummary'
x-nullable: true x-nullable: true
x-omitempty: true x-omitempty: true
purchaseFrom: purchaseFrom:
@ -197,24 +177,6 @@ definitions:
warrantyExpires: warrantyExpires:
type: string type: string
type: object type: object
repo.ItemPatch:
properties:
id:
type: string
quantity:
type: integer
x-nullable: true
x-omitempty: true
type: object
repo.ItemPath:
properties:
id:
type: string
name:
type: string
type:
$ref: '#/definitions/repo.ItemType'
type: object
repo.ItemSummary: repo.ItemSummary:
properties: properties:
archived: archived:
@ -225,8 +187,6 @@ definitions:
type: string type: string
id: id:
type: string type: string
imageId:
type: string
insured: insured:
type: boolean type: boolean
labels: labels:
@ -234,8 +194,7 @@ definitions:
$ref: '#/definitions/repo.LabelSummary' $ref: '#/definitions/repo.LabelSummary'
type: array type: array
location: location:
allOf: $ref: '#/definitions/repo.LocationSummary'
- $ref: '#/definitions/repo.LocationSummary'
description: Edges description: Edges
x-nullable: true x-nullable: true
x-omitempty: true x-omitempty: true
@ -249,14 +208,6 @@ definitions:
updatedAt: updatedAt:
type: string type: string
type: object type: object
repo.ItemType:
enum:
- location
- item
type: string
x-enum-varnames:
- ItemTypeLocation
- ItemTypeItem
repo.ItemUpdate: repo.ItemUpdate:
properties: properties:
archived: archived:
@ -322,6 +273,7 @@ definitions:
warrantyDetails: warrantyDetails:
type: string type: string
warrantyExpires: warrantyExpires:
description: Sold
type: string type: string
type: object type: object
repo.LabelCreate: repo.LabelCreate:
@ -329,14 +281,9 @@ definitions:
color: color:
type: string type: string
description: description:
maxLength: 255
type: string type: string
name: name:
maxLength: 255
minLength: 1
type: string type: string
required:
- name
type: object type: object
repo.LabelOut: repo.LabelOut:
properties: properties:
@ -346,6 +293,10 @@ definitions:
type: string type: string
id: id:
type: string type: string
items:
items:
$ref: '#/definitions/repo.ItemSummary'
type: array
name: name:
type: string type: string
updatedAt: updatedAt:
@ -386,6 +337,10 @@ definitions:
type: string type: string
id: id:
type: string type: string
items:
items:
$ref: '#/definitions/repo.ItemSummary'
type: array
name: name:
type: string type: string
parent: parent:
@ -436,6 +391,7 @@ definitions:
repo.MaintenanceEntry: repo.MaintenanceEntry:
properties: properties:
completedDate: completedDate:
description: Sold
type: string type: string
cost: cost:
example: "0" example: "0"
@ -447,11 +403,13 @@ definitions:
name: name:
type: string type: string
scheduledDate: scheduledDate:
description: Sold
type: string type: string
type: object type: object
repo.MaintenanceEntryCreate: repo.MaintenanceEntryCreate:
properties: properties:
completedDate: completedDate:
description: Sold
type: string type: string
cost: cost:
example: "0" example: "0"
@ -461,13 +419,13 @@ definitions:
name: name:
type: string type: string
scheduledDate: scheduledDate:
description: Sold
type: string type: string
required:
- name
type: object type: object
repo.MaintenanceEntryUpdate: repo.MaintenanceEntryUpdate:
properties: properties:
completedDate: completedDate:
description: Sold
type: string type: string
cost: cost:
example: "0" example: "0"
@ -477,6 +435,7 @@ definitions:
name: name:
type: string type: string
scheduledDate: scheduledDate:
description: Sold
type: string type: string
type: object type: object
repo.MaintenanceLog: repo.MaintenanceLog:
@ -620,6 +579,28 @@ definitions:
value: value:
type: number type: number
type: object type: object
server.ErrorResponse:
properties:
error:
type: string
fields:
additionalProperties:
type: string
type: object
type: object
server.Result:
properties:
details: {}
error:
type: boolean
item: {}
message:
type: string
type: object
server.Results:
properties:
items: {}
type: object
services.UserRegistration: services.UserRegistration:
properties: properties:
email: email:
@ -631,7 +612,12 @@ definitions:
token: token:
type: string type: string
type: object type: object
v1.APISummary: v1.ActionAmountResult:
properties:
completed:
type: integer
type: object
v1.ApiSummary:
properties: properties:
allowRegistration: allowRegistration:
type: boolean type: boolean
@ -650,11 +636,6 @@ definitions:
type: string type: string
type: array type: array
type: object type: object
v1.ActionAmountResult:
properties:
completed:
type: integer
type: object
v1.Build: v1.Build:
properties: properties:
buildTime: buildTime:
@ -685,26 +666,13 @@ definitions:
expiresAt: expiresAt:
type: string type: string
uses: uses:
maximum: 100
minimum: 1
type: integer type: integer
required:
- uses
type: object type: object
v1.ItemAttachmentToken: v1.ItemAttachmentToken:
properties: properties:
token: token:
type: string type: string
type: object type: object
v1.LoginForm:
properties:
password:
type: string
stayLoggedIn:
type: boolean
username:
type: string
type: object
v1.TokenResponse: v1.TokenResponse:
properties: properties:
attachmentToken: attachmentToken:
@ -714,21 +682,12 @@ definitions:
token: token:
type: string type: string
type: object type: object
v1.Wrapped:
properties:
item: {}
type: object
validate.ErrorResponse:
properties:
error:
type: string
fields:
type: string
type: object
info: info:
contact: contact:
name: Don't name: Don't
description: Track, Manage, and Organize your Things. description: Track, Manage, and Organize your Shit.
license:
name: MIT
title: Homebox API title: Homebox API
version: "1.0" version: "1.0"
paths: paths:
@ -762,21 +721,6 @@ paths:
summary: Ensures Import Refs summary: Ensures Import Refs
tags: tags:
- Actions - Actions
/v1/actions/set-primary-photos:
post:
description: Sets the first photo of each item as the primary photo
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/v1.ActionAmountResult'
security:
- Bearer: []
summary: Set Primary Photos
tags:
- Actions
/v1/actions/zero-item-time-fields: /v1/actions/zero-item-time-fields:
post: post:
description: Resets all item date fields to the beginning of the day description: Resets all item date fields to the beginning of the day
@ -812,18 +756,6 @@ paths:
summary: Get Item by Asset ID summary: Get Item by Asset ID
tags: tags:
- Items - Items
/v1/currency:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/currencies.Currency'
summary: Currency
tags:
- Base
/v1/groups: /v1/groups:
get: get:
produces: produces:
@ -977,13 +909,6 @@ paths:
type: string type: string
name: locations name: locations
type: array type: array
- collectionFormat: multi
description: parent Ids
in: query
items:
type: string
name: parentIds
type: array
produces: produces:
- application/json - application/json
responses: responses:
@ -1007,8 +932,8 @@ paths:
produces: produces:
- application/json - application/json
responses: responses:
"201": "200":
description: Created description: OK
schema: schema:
$ref: '#/definitions/repo.ItemSummary' $ref: '#/definitions/repo.ItemSummary'
security: security:
@ -1053,31 +978,6 @@ paths:
summary: Get Item summary: Get Item
tags: tags:
- Items - Items
patch:
parameters:
- description: Item ID
in: path
name: id
required: true
type: string
- description: Item Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.ItemPatch'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.ItemOut'
security:
- Bearer: []
summary: Update Item
tags:
- Items
put: put:
parameters: parameters:
- description: Item ID - description: Item ID
@ -1136,7 +1036,7 @@ paths:
"422": "422":
description: Unprocessable Entity description: Unprocessable Entity
schema: schema:
$ref: '#/definitions/validate.ErrorResponse' $ref: '#/definitions/server.ErrorResponse'
security: security:
- Bearer: [] - Bearer: []
summary: Create Item Attachment summary: Create Item Attachment
@ -1240,8 +1140,8 @@ paths:
produces: produces:
- application/json - application/json
responses: responses:
"201": "200":
description: Created description: OK
schema: schema:
$ref: '#/definitions/repo.MaintenanceEntry' $ref: '#/definitions/repo.MaintenanceEntry'
security: security:
@ -1281,28 +1181,6 @@ paths:
summary: Update Maintenance Entry summary: Update Maintenance Entry
tags: tags:
- Maintenance - Maintenance
/v1/items/{id}/path:
get:
parameters:
- description: Item ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.ItemPath'
type: array
security:
- Bearer: []
summary: Get the full path of an item
tags:
- Items
/v1/items/export: /v1/items/export:
get: get:
responses: responses:
@ -1373,9 +1251,14 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
items: allOf:
$ref: '#/definitions/repo.LabelOut' - $ref: '#/definitions/server.Results'
type: array - properties:
items:
items:
$ref: '#/definitions/repo.LabelOut'
type: array
type: object
security: security:
- Bearer: [] - Bearer: []
summary: Get All Labels summary: Get All Labels
@ -1470,9 +1353,14 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
items: allOf:
$ref: '#/definitions/repo.LocationOutCount' - $ref: '#/definitions/server.Results'
type: array - properties:
items:
items:
$ref: '#/definitions/repo.LocationOutCount'
type: array
type: object
security: security:
- Bearer: [] - Bearer: []
summary: Get All Locations summary: Get All Locations
@ -1573,9 +1461,14 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
items: allOf:
$ref: '#/definitions/repo.TreeItem' - $ref: '#/definitions/server.Results'
type: array - properties:
items:
items:
$ref: '#/definitions/repo.TreeItem'
type: array
type: object
security: security:
- Bearer: [] - Bearer: []
summary: Get Locations Tree summary: Get Locations Tree
@ -1589,9 +1482,14 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
items: allOf:
$ref: '#/definitions/repo.NotifierOut' - $ref: '#/definitions/server.Results'
type: array - properties:
items:
items:
$ref: '#/definitions/repo.NotifierOut'
type: array
type: object
security: security:
- Bearer: [] - Bearer: []
summary: Get Notifiers summary: Get Notifiers
@ -1720,7 +1618,7 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/v1.APISummary' $ref: '#/definitions/v1.ApiSummary'
summary: Application Info summary: Application Info
tags: tags:
- Base - Base
@ -1757,16 +1655,6 @@ paths:
in: formData in: formData
name: password name: password
type: string type: string
- description: Login Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/v1.LoginForm'
- description: auth provider
in: query
name: provider
type: string
produces: produces:
- application/json - application/json
responses: responses:
@ -1837,7 +1725,7 @@ paths:
description: OK description: OK
schema: schema:
allOf: allOf:
- $ref: '#/definitions/v1.Wrapped' - $ref: '#/definitions/server.Result'
- properties: - properties:
item: item:
$ref: '#/definitions/repo.UserOut' $ref: '#/definitions/repo.UserOut'
@ -1862,7 +1750,7 @@ paths:
description: OK description: OK
schema: schema:
allOf: allOf:
- $ref: '#/definitions/v1.Wrapped' - $ref: '#/definitions/server.Result'
- properties: - properties:
item: item:
$ref: '#/definitions/repo.UserUpdate' $ref: '#/definitions/repo.UserUpdate'

View file

@ -1,77 +1,60 @@
module github.com/hay-kot/homebox/backend module github.com/hay-kot/homebox/backend
go 1.23.0 go 1.19
toolchain go1.24.2
require ( require (
ariga.io/atlas v0.32.0 ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb
entgo.io/ent v0.14.4 entgo.io/ent v0.11.8
github.com/ardanlabs/conf/v3 v3.7.1 github.com/ardanlabs/conf/v3 v3.1.4
github.com/containrrr/shoutrrr v0.8.0 github.com/containrrr/shoutrrr v0.7.1
github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/chi/v5 v5.0.8
github.com/go-playground/validator/v10 v10.26.0 github.com/go-playground/validator/v10 v10.11.2
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/gocarina/gocsv v0.0.0-20230219202803-bcce7dc8d0bb
github.com/google/uuid v1.6.0 github.com/google/uuid v1.3.0
github.com/gorilla/schema v1.4.1 github.com/gorilla/schema v1.2.0
github.com/hay-kot/httpkit v0.0.11 github.com/mattn/go-sqlite3 v1.14.16
github.com/mattn/go-sqlite3 v1.14.27 github.com/rs/zerolog v1.29.0
github.com/olahol/melody v1.2.1 github.com/stretchr/testify v1.8.2
github.com/pkg/errors v0.9.1 github.com/swaggo/http-swagger v1.3.3
github.com/rs/zerolog v1.34.0 github.com/swaggo/swag v1.8.10
github.com/stretchr/testify v1.10.0 github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/swaggo/http-swagger/v2 v2.0.2 github.com/yeqown/go-qrcode/writer/standard v1.2.1
github.com/swaggo/swag v1.16.4 golang.org/x/crypto v0.7.0
github.com/yeqown/go-qrcode/v2 v2.2.5
github.com/yeqown/go-qrcode/writer/standard v1.2.5
golang.org/x/crypto v0.37.0
modernc.org/sqlite v1.37.0
) )
require ( require (
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect github.com/fogleman/gg v1.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-openapi/inflect v0.21.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.20.7 // indirect
github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/hcl/v2 v2.15.0 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/swaggo/files v1.0.0 // indirect
github.com/swaggo/files/v2 v2.0.2 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect github.com/zclconf/go-cty v1.12.1 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/mod v0.8.0 // indirect
golang.org/x/image v0.26.0 // indirect golang.org/x/net v0.8.0 // indirect
golang.org/x/mod v0.24.0 // indirect golang.org/x/sys v0.6.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/text v0.8.0 // indirect
golang.org/x/sync v0.13.0 // indirect golang.org/x/tools v0.6.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.62.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.9.1 // indirect
) )

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
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,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,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,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,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,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,Description 6,FALSE,,39351,Honeywell,,Amazon,65.98,09/30/2020,,09/30/2020,,,,09/30/2020,
1 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
2 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
3 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
4 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
5 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
6 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
7 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

View file

@ -0,0 +1,7 @@
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 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 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 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 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 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 Description 6 FALSE 39351 Honeywell Amazon 65.98 09/30/2020 09/30/2020 09/30/2020
1 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
2 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
3 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
4 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
5 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
6 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
7 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

View file

@ -1,91 +0,0 @@
// Package eventbus provides an interface for event bus.
package eventbus
import (
"context"
"sync"
"github.com/google/uuid"
)
type Event string
const (
EventLabelMutation Event = "label.mutation"
EventLocationMutation Event = "location.mutation"
EventItemMutation Event = "item.mutation"
)
type GroupMutationEvent struct {
GID uuid.UUID
}
type eventData struct {
event Event
data any
}
type EventBus struct {
started bool
ch chan eventData
mu sync.RWMutex
subscribers map[Event][]func(any)
}
func New() *EventBus {
return &EventBus{
ch: make(chan eventData, 100),
subscribers: map[Event][]func(any){
EventLabelMutation: {},
EventLocationMutation: {},
EventItemMutation: {},
},
}
}
func (e *EventBus) Run(ctx context.Context) error {
if e.started {
panic("event bus already started")
}
e.started = true
for {
select {
case <-ctx.Done():
return nil
case event := <-e.ch:
e.mu.RLock()
arr, ok := e.subscribers[event.event]
e.mu.RUnlock()
if !ok {
continue
}
for _, fn := range arr {
fn(event.data)
}
}
}
}
func (e *EventBus) Publish(event Event, data any) {
e.ch <- eventData{
event: event,
data: data,
}
}
func (e *EventBus) Subscribe(event Event, fn func(any)) {
e.mu.Lock()
defer e.mu.Unlock()
arr, ok := e.subscribers[event]
if !ok {
panic("event not found")
}
e.subscribers[event] = append(arr, fn)
}

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package reporting package reporting
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
@ -9,7 +8,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/data/types" "github.com/hay-kot/homebox/backend/internal/data/types"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -152,8 +150,8 @@ func (s *IOSheet) Read(data io.Reader) error {
return nil return nil
} }
// ReadItems writes the sheet to a writer. // Write writes the sheet to a writer.
func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos) error { func (s *IOSheet) ReadItems(items []repo.ItemOut) {
s.Rows = make([]ExportTSVRow, len(items)) s.Rows = make([]ExportTSVRow, len(items))
extraHeaders := map[string]struct{}{} extraHeaders := map[string]struct{}{}
@ -162,15 +160,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
item := items[i] item := items[i]
// TODO: Support fetching nested locations // TODO: Support fetching nested locations
locID := item.Location.ID locString := LocationString{item.Location.Name}
locPaths, err := repos.Locations.PathForLoc(context.Background(), GID, locID)
if err != nil {
log.Error().Err(err).Msg("could not get location path")
return err
}
locString := fromPathSlice(locPaths)
labelString := make([]string, len(item.Labels)) labelString := make([]string, len(item.Labels))
@ -248,11 +238,9 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
for _, h := range customHeaders { for _, h := range customHeaders {
s.headers = append(s.headers, "HB.field."+h) s.headers = append(s.headers, "HB.field."+h)
} }
return nil
} }
// TSV writes the current sheet to a writer in TSV format. // Writes the current sheet to a writer in TSV format.
func (s *IOSheet) TSV() ([][]string, error) { func (s *IOSheet) TSV() ([][]string, error) {
memcsv := make([][]string, len(s.Rows)+1) memcsv := make([][]string, len(s.Rows)+1)

View file

@ -9,7 +9,6 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var ( var (
@ -21,6 +20,12 @@ var (
//go:embed .testdata/import/types.csv //go:embed .testdata/import/types.csv
customTypesImportCSV []byte customTypesImportCSV []byte
//go:embed .testdata/import.csv
CSVData_Comma []byte
//go:embed .testdata/import.tsv
CSVData_Tab []byte
) )
func TestSheet_Read(t *testing.T) { func TestSheet_Read(t *testing.T) {
@ -104,9 +109,9 @@ func TestSheet_Read(t *testing.T) {
switch { switch {
case tt.wantErr: case tt.wantErr:
require.Error(t, err) assert.Error(t, err)
default: default:
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, tt.want, sheet.Rows) assert.ElementsMatch(t, tt.want, sheet.Rows)
} }
}) })
@ -184,7 +189,7 @@ func Test_determineSeparator(t *testing.T) {
{ {
name: "comma", name: "comma",
args: args{ args: args{
data: []byte("a,b,c"), data: CSVData_Comma,
}, },
want: ',', want: ',',
wantErr: false, wantErr: false,
@ -192,7 +197,7 @@ func Test_determineSeparator(t *testing.T) {
{ {
name: "tab", name: "tab",
args: args{ args: args{
data: []byte("a\tb\tc"), data: CSVData_Tab,
}, },
want: '\t', want: '\t',
wantErr: false, wantErr: false,

View file

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

View file

@ -32,7 +32,7 @@ func (svc *ItemService) Create(ctx Context, item repo.ItemCreate) (repo.ItemOut,
return repo.ItemOut{}, err return repo.ItemOut{}, err
} }
item.AssetID = highest + 1 item.AssetID = repo.AssetID(highest + 1)
} }
return svc.repo.Items.Create(ctx, ctx.GID, item) return svc.repo.Items.Create(ctx, ctx.GID, item)
@ -53,7 +53,7 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int,
for _, item := range items { for _, item := range items {
highest++ highest++
err = svc.repo.Items.SetAssetID(ctx, GID, item.ID, highest) err = svc.repo.Items.SetAssetID(ctx, GID, item.ID, repo.AssetID(highest))
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -337,10 +337,7 @@ func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]strin
sheet := reporting.IOSheet{} sheet := reporting.IOSheet{}
err = sheet.ReadItems(ctx, items, GID, svc.repo) sheet.ReadItems(items)
if err != nil {
return nil, err
}
return sheet.TSV() return sheet.TSV()
} }

View file

@ -12,8 +12,8 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentID uuid.UUID) (*ent.Document, error) { func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentId uuid.UUID) (*ent.Document, error) {
attachment, err := svc.repo.Attachments.Get(ctx, attachmentID) attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -21,9 +21,9 @@ func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentID uuid.UU
return attachment.Edges.Document, nil return attachment.Edges.Document, nil
} }
func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) { func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) {
// Update Attachment // Update Attachment
attachment, err := svc.repo.Attachments.Update(ctx, data.ID, data) attachment, err := svc.repo.Attachments.Update(ctx, data.ID, attachment.Type(data.Type))
if err != nil { if err != nil {
return repo.ItemOut{}, err return repo.ItemOut{}, err
} }
@ -35,15 +35,15 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *re
return repo.ItemOut{}, err return repo.ItemOut{}, err
} }
return svc.repo.Items.GetOneByGroup(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
// Table and Items table. The file provided via the reader is stored on the file system based on the provided // Table and Items table. The file provided via the reader is stored on the file system based on the provided
// relative path during construction of the service. // relative path during construction of the service.
func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) { func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) {
// Get the Item // Get the Item
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID) _, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
if err != nil { if err != nil {
return repo.ItemOut{}, err return repo.ItemOut{}, err
} }
@ -56,29 +56,29 @@ func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename st
} }
// Create the attachment // Create the attachment
_, err = svc.repo.Attachments.Create(ctx, itemID, doc.ID, attachmentType) _, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachmentType)
if err != nil { if err != nil {
log.Err(err).Msg("failed to create attachment") log.Err(err).Msg("failed to create attachment")
return repo.ItemOut{}, err return repo.ItemOut{}, err
} }
return svc.repo.Items.GetOneByGroup(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 {
// Get the Item // Get the Item
_, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemID) _, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemId)
if err != nil { if err != nil {
return err return err
} }
attachment, err := svc.repo.Attachments.Get(ctx, attachmentID) attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
if err != nil { if err != nil {
return err return err
} }
// Delete the attachment // Delete the attachment
err = svc.repo.Attachments.Delete(ctx, attachmentID) err = svc.repo.Attachments.Delete(ctx, attachmentId)
if err != nil { if err != nil {
return err return err
} }

View file

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

View file

@ -16,7 +16,7 @@ var (
oneWeek = time.Hour * 24 * 7 oneWeek = time.Hour * 24 * 7
ErrorInvalidLogin = errors.New("invalid username or password") ErrorInvalidLogin = errors.New("invalid username or password")
ErrorInvalidToken = errors.New("invalid token") ErrorInvalidToken = errors.New("invalid token")
ErrorTokenIDMismatch = errors.New("token id mismatch") ErrorTokenIdMismatch = errors.New("token id mismatch")
) )
type UserService struct { type UserService struct {
@ -61,7 +61,6 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
switch data.GroupToken { switch data.GroupToken {
case "": case "":
log.Debug().Msg("creating new group")
creatingGroup = true creatingGroup = true
group, err = svc.repos.Groups.GroupCreate(ctx, "Home") group, err = svc.repos.Groups.GroupCreate(ctx, "Home")
if err != nil { if err != nil {
@ -69,7 +68,6 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
return repo.UserOut{}, err return repo.UserOut{}, err
} }
default: default:
log.Debug().Msg("joining existing group")
token, err = svc.repos.Groups.InvitationGet(ctx, hasher.HashToken(data.GroupToken)) token, err = svc.repos.Groups.InvitationGet(ctx, hasher.HashToken(data.GroupToken))
if err != nil { if err != nil {
log.Err(err).Msg("Failed to get invitation token") log.Err(err).Msg("Failed to get invitation token")
@ -92,21 +90,18 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
if err != nil { if err != nil {
return repo.UserOut{}, err return repo.UserOut{}, err
} }
log.Debug().Msg("user created")
// Create the default labels and locations for the group. // Create the default labels and locations for the group.
if creatingGroup { if creatingGroup {
log.Debug().Msg("creating default labels")
for _, label := range defaultLabels() { for _, label := range defaultLabels() {
_, err := svc.repos.Labels.Create(ctx, usr.GroupID, label) _, err := svc.repos.Labels.Create(ctx, group.ID, label)
if err != nil { if err != nil {
return repo.UserOut{}, err return repo.UserOut{}, err
} }
} }
log.Debug().Msg("creating default locations")
for _, location := range defaultLocations() { for _, location := range defaultLocations() {
_, err := svc.repos.Locations.Create(ctx, usr.GroupID, location) _, err := svc.repos.Locations.Create(ctx, group.ID, location)
if err != nil { if err != nil {
return repo.UserOut{}, err return repo.UserOut{}, err
} }
@ -115,7 +110,6 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
// Decrement the invitation token if it was used. // Decrement the invitation token if it was used.
if token.ID != uuid.Nil { if token.ID != uuid.Nil {
log.Debug().Msg("decrementing invitation token")
err = svc.repos.Groups.InvitationUpdate(ctx, token.ID, token.Uses-1) err = svc.repos.Groups.InvitationUpdate(ctx, token.ID, token.Uses-1)
if err != nil { if err != nil {
log.Err(err).Msg("Failed to update invitation token") log.Err(err).Msg("Failed to update invitation token")
@ -138,24 +132,18 @@ func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo.
return repo.UserOut{}, err return repo.UserOut{}, err
} }
return svc.repos.Users.GetOneID(ctx, ID) return svc.repos.Users.GetOneId(ctx, ID)
} }
// ============================================================================ // ============================================================================
// User Authentication // User Authentication
func (svc *UserService) createSessionToken(ctx context.Context, userID uuid.UUID, extendedSession bool) (UserAuthTokenDetail, error) { func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID) (UserAuthTokenDetail, error) {
attachmentToken := hasher.GenerateToken() attachmentToken := hasher.GenerateToken()
expiresAt := time.Now().Add(oneWeek)
if extendedSession {
expiresAt = time.Now().Add(oneWeek * 4)
}
attachmentData := repo.UserAuthTokenCreate{ attachmentData := repo.UserAuthTokenCreate{
UserID: userID, UserID: userId,
TokenHash: attachmentToken.Hash, TokenHash: attachmentToken.Hash,
ExpiresAt: expiresAt, ExpiresAt: time.Now().Add(oneWeek),
} }
_, err := svc.repos.AuthTokens.CreateToken(ctx, attachmentData, authroles.RoleAttachments) _, err := svc.repos.AuthTokens.CreateToken(ctx, attachmentData, authroles.RoleAttachments)
@ -165,9 +153,9 @@ func (svc *UserService) createSessionToken(ctx context.Context, userID uuid.UUID
userToken := hasher.GenerateToken() userToken := hasher.GenerateToken()
data := repo.UserAuthTokenCreate{ data := repo.UserAuthTokenCreate{
UserID: userID, UserID: userId,
TokenHash: userToken.Hash, TokenHash: userToken.Hash,
ExpiresAt: expiresAt, ExpiresAt: time.Now().Add(oneWeek),
} }
created, err := svc.repos.AuthTokens.CreateToken(ctx, data, authroles.RoleUser) created, err := svc.repos.AuthTokens.CreateToken(ctx, data, authroles.RoleUser)
@ -182,7 +170,7 @@ func (svc *UserService) createSessionToken(ctx context.Context, userID uuid.UUID
}, nil }, nil
} }
func (svc *UserService) Login(ctx context.Context, username, password string, extendedSession bool) (UserAuthTokenDetail, error) { func (svc *UserService) Login(ctx context.Context, username, password string) (UserAuthTokenDetail, error) {
usr, err := svc.repos.Users.GetOneEmail(ctx, username) usr, err := svc.repos.Users.GetOneEmail(ctx, username)
if err != nil { if err != nil {
// SECURITY: Perform hash to ensure response times are the same // SECURITY: Perform hash to ensure response times are the same
@ -194,7 +182,7 @@ func (svc *UserService) Login(ctx context.Context, username, password string, ex
return UserAuthTokenDetail{}, ErrorInvalidLogin return UserAuthTokenDetail{}, ErrorInvalidLogin
} }
return svc.createSessionToken(ctx, usr.ID, extendedSession) return svc.createSessionToken(ctx, usr.ID)
} }
func (svc *UserService) Logout(ctx context.Context, token string) error { func (svc *UserService) Logout(ctx context.Context, token string) error {
@ -211,7 +199,7 @@ func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthT
return UserAuthTokenDetail{}, ErrorInvalidToken return UserAuthTokenDetail{}, ErrorInvalidToken
} }
return svc.createSessionToken(ctx, dbToken.ID, false) return svc.createSessionToken(ctx, dbToken.ID)
} }
// DeleteSelf deletes the user that is currently logged based of the provided UUID // DeleteSelf deletes the user that is currently logged based of the provided UUID
@ -222,7 +210,7 @@ func (svc *UserService) DeleteSelf(ctx context.Context, ID uuid.UUID) error {
} }
func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) { func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) {
usr, err := svc.repos.Users.GetOneID(ctx, ctx.UID) usr, err := svc.repos.Users.GetOneId(ctx, ctx.UID)
if err != nil { if err != nil {
return false return false
} }

View file

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
@ -26,14 +25,11 @@ type Attachment struct {
UpdatedAt time.Time `json:"updated_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"`
// Type holds the value of the "type" field. // Type holds the value of the "type" field.
Type attachment.Type `json:"type,omitempty"` Type attachment.Type `json:"type,omitempty"`
// Primary holds the value of the "primary" field.
Primary bool `json:"primary,omitempty"`
// Edges holds the relations/edges for other nodes in the graph. // Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AttachmentQuery when eager-loading is set. // The values are being populated by the AttachmentQuery when eager-loading is set.
Edges AttachmentEdges `json:"edges"` Edges AttachmentEdges `json:"edges"`
document_attachments *uuid.UUID document_attachments *uuid.UUID
item_attachments *uuid.UUID item_attachments *uuid.UUID
selectValues sql.SelectValues
} }
// AttachmentEdges holds the relations/edges for other nodes in the graph. // AttachmentEdges holds the relations/edges for other nodes in the graph.
@ -78,8 +74,6 @@ func (*Attachment) 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 attachment.FieldPrimary:
values[i] = new(sql.NullBool)
case attachment.FieldType: case attachment.FieldType:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case attachment.FieldCreatedAt, attachment.FieldUpdatedAt: case attachment.FieldCreatedAt, attachment.FieldUpdatedAt:
@ -91,7 +85,7 @@ func (*Attachment) scanValues(columns []string) ([]any, error) {
case attachment.ForeignKeys[1]: // item_attachments case attachment.ForeignKeys[1]: // item_attachments
values[i] = &sql.NullScanner{S: new(uuid.UUID)} values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default: default:
values[i] = new(sql.UnknownType) return nil, fmt.Errorf("unexpected column %q for type Attachment", columns[i])
} }
} }
return values, nil return values, nil
@ -129,12 +123,6 @@ func (a *Attachment) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
a.Type = attachment.Type(value.String) a.Type = attachment.Type(value.String)
} }
case attachment.FieldPrimary:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field primary", values[i])
} else if value.Valid {
a.Primary = value.Bool
}
case attachment.ForeignKeys[0]: case attachment.ForeignKeys[0]:
if value, ok := values[i].(*sql.NullScanner); !ok { if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field document_attachments", values[i]) return fmt.Errorf("unexpected type %T for field document_attachments", values[i])
@ -149,19 +137,11 @@ func (a *Attachment) assignValues(columns []string, values []any) error {
a.item_attachments = new(uuid.UUID) a.item_attachments = new(uuid.UUID)
*a.item_attachments = *value.S.(*uuid.UUID) *a.item_attachments = *value.S.(*uuid.UUID)
} }
default:
a.selectValues.Set(columns[i], values[i])
} }
} }
return nil return nil
} }
// Value returns the ent.Value that was dynamically selected and assigned to the Attachment.
// This includes values selected through modifiers, order, etc.
func (a *Attachment) Value(name string) (ent.Value, error) {
return a.selectValues.Get(name)
}
// QueryItem queries the "item" edge of the Attachment entity. // QueryItem queries the "item" edge of the Attachment entity.
func (a *Attachment) QueryItem() *ItemQuery { func (a *Attachment) QueryItem() *ItemQuery {
return NewAttachmentClient(a.config).QueryItem(a) return NewAttachmentClient(a.config).QueryItem(a)
@ -203,9 +183,6 @@ func (a *Attachment) String() string {
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("type=") builder.WriteString("type=")
builder.WriteString(fmt.Sprintf("%v", a.Type)) builder.WriteString(fmt.Sprintf("%v", a.Type))
builder.WriteString(", ")
builder.WriteString("primary=")
builder.WriteString(fmt.Sprintf("%v", a.Primary))
builder.WriteByte(')') builder.WriteByte(')')
return builder.String() return builder.String()
} }

View file

@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"time" "time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -22,8 +20,6 @@ const (
FieldUpdatedAt = "updated_at" FieldUpdatedAt = "updated_at"
// FieldType holds the string denoting the type field in the database. // FieldType holds the string denoting the type field in the database.
FieldType = "type" FieldType = "type"
// FieldPrimary holds the string denoting the primary field in the database.
FieldPrimary = "primary"
// EdgeItem holds the string denoting the item edge name in mutations. // EdgeItem holds the string denoting the item edge name in mutations.
EdgeItem = "item" EdgeItem = "item"
// EdgeDocument holds the string denoting the document edge name in mutations. // EdgeDocument holds the string denoting the document edge name in mutations.
@ -52,7 +48,6 @@ var Columns = []string{
FieldCreatedAt, FieldCreatedAt,
FieldUpdatedAt, FieldUpdatedAt,
FieldType, FieldType,
FieldPrimary,
} }
// ForeignKeys holds the SQL foreign-keys that are owned by the "attachments" // ForeignKeys holds the SQL foreign-keys that are owned by the "attachments"
@ -84,8 +79,6 @@ var (
DefaultUpdatedAt func() time.Time DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time UpdateDefaultUpdatedAt func() time.Time
// DefaultPrimary holds the default value on creation for the "primary" field.
DefaultPrimary bool
// DefaultID holds the default value on creation for the "id" field. // DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID DefaultID func() uuid.UUID
) )
@ -118,59 +111,3 @@ func TypeValidator(_type Type) error {
return fmt.Errorf("attachment: invalid enum value for type field: %q", _type) return fmt.Errorf("attachment: invalid enum value for type field: %q", _type)
} }
} }
// OrderOption defines the ordering options for the Attachment queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByType orders the results by the type field.
func ByType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldType, opts...).ToFunc()
}
// ByPrimary orders the results by the primary field.
func ByPrimary(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPrimary, opts...).ToFunc()
}
// ByItemField orders the results by item field.
func ByItemField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newItemStep(), sql.OrderByField(field, opts...))
}
}
// ByDocumentField orders the results by document field.
func ByDocumentField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newDocumentStep(), sql.OrderByField(field, opts...))
}
}
func newItemStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ItemInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ItemTable, ItemColumn),
)
}
func newDocumentStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
)
}

View file

@ -66,11 +66,6 @@ func UpdatedAt(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldUpdatedAt, v)) return predicate.Attachment(sql.FieldEQ(FieldUpdatedAt, v))
} }
// Primary applies equality check predicate on the "primary" field. It's identical to PrimaryEQ.
func Primary(v bool) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldPrimary, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field. // CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Attachment { func CreatedAtEQ(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldCreatedAt, v)) return predicate.Attachment(sql.FieldEQ(FieldCreatedAt, v))
@ -171,16 +166,6 @@ func TypeNotIn(vs ...Type) predicate.Attachment {
return predicate.Attachment(sql.FieldNotIn(FieldType, vs...)) return predicate.Attachment(sql.FieldNotIn(FieldType, vs...))
} }
// PrimaryEQ applies the EQ predicate on the "primary" field.
func PrimaryEQ(v bool) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldPrimary, v))
}
// PrimaryNEQ applies the NEQ predicate on the "primary" field.
func PrimaryNEQ(v bool) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldPrimary, v))
}
// HasItem applies the HasEdge predicate on the "item" edge. // HasItem applies the HasEdge predicate on the "item" edge.
func HasItem() predicate.Attachment { func HasItem() predicate.Attachment {
return predicate.Attachment(func(s *sql.Selector) { return predicate.Attachment(func(s *sql.Selector) {
@ -195,7 +180,11 @@ func HasItem() predicate.Attachment {
// HasItemWith applies the HasEdge predicate on the "item" edge with a given conditions (other predicates). // HasItemWith applies the HasEdge predicate on the "item" edge with a given conditions (other predicates).
func HasItemWith(preds ...predicate.Item) predicate.Attachment { func HasItemWith(preds ...predicate.Item) predicate.Attachment {
return predicate.Attachment(func(s *sql.Selector) { return predicate.Attachment(func(s *sql.Selector) {
step := newItemStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ItemInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ItemTable, ItemColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -218,7 +207,11 @@ func HasDocument() predicate.Attachment {
// HasDocumentWith applies the HasEdge predicate on the "document" edge with a given conditions (other predicates). // HasDocumentWith applies the HasEdge predicate on the "document" edge with a given conditions (other predicates).
func HasDocumentWith(preds ...predicate.Document) predicate.Attachment { func HasDocumentWith(preds ...predicate.Document) predicate.Attachment {
return predicate.Attachment(func(s *sql.Selector) { return predicate.Attachment(func(s *sql.Selector) {
step := newDocumentStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -229,15 +222,32 @@ func HasDocumentWith(preds ...predicate.Document) predicate.Attachment {
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.Attachment) predicate.Attachment { func And(predicates ...predicate.Attachment) predicate.Attachment {
return predicate.Attachment(sql.AndPredicates(predicates...)) return predicate.Attachment(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for _, p := range predicates {
p(s1)
}
s.Where(s1.P())
})
} }
// Or groups predicates with the OR operator between them. // Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Attachment) predicate.Attachment { func Or(predicates ...predicate.Attachment) predicate.Attachment {
return predicate.Attachment(sql.OrPredicates(predicates...)) return predicate.Attachment(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for i, p := range predicates {
if i > 0 {
s1.Or()
}
p(s1)
}
s.Where(s1.P())
})
} }
// Not applies the not operator on the given predicate. // Not applies the not operator on the given predicate.
func Not(p predicate.Attachment) predicate.Attachment { func Not(p predicate.Attachment) predicate.Attachment {
return predicate.Attachment(sql.NotPredicates(p)) return predicate.Attachment(func(s *sql.Selector) {
p(s.Not())
})
} }

View file

@ -65,20 +65,6 @@ func (ac *AttachmentCreate) SetNillableType(a *attachment.Type) *AttachmentCreat
return ac return ac
} }
// SetPrimary sets the "primary" field.
func (ac *AttachmentCreate) SetPrimary(b bool) *AttachmentCreate {
ac.mutation.SetPrimary(b)
return ac
}
// SetNillablePrimary sets the "primary" field if the given value is not nil.
func (ac *AttachmentCreate) SetNillablePrimary(b *bool) *AttachmentCreate {
if b != nil {
ac.SetPrimary(*b)
}
return ac
}
// SetID sets the "id" field. // SetID sets the "id" field.
func (ac *AttachmentCreate) SetID(u uuid.UUID) *AttachmentCreate { func (ac *AttachmentCreate) SetID(u uuid.UUID) *AttachmentCreate {
ac.mutation.SetID(u) ac.mutation.SetID(u)
@ -123,7 +109,7 @@ func (ac *AttachmentCreate) Mutation() *AttachmentMutation {
// Save creates the Attachment in the database. // Save creates the Attachment in the database.
func (ac *AttachmentCreate) Save(ctx context.Context) (*Attachment, error) { func (ac *AttachmentCreate) Save(ctx context.Context) (*Attachment, error) {
ac.defaults() ac.defaults()
return withHooks(ctx, ac.sqlSave, ac.mutation, ac.hooks) return withHooks[*Attachment, AttachmentMutation](ctx, ac.sqlSave, ac.mutation, ac.hooks)
} }
// SaveX calls Save and panics if Save returns an error. // SaveX calls Save and panics if Save returns an error.
@ -162,10 +148,6 @@ func (ac *AttachmentCreate) defaults() {
v := attachment.DefaultType v := attachment.DefaultType
ac.mutation.SetType(v) ac.mutation.SetType(v)
} }
if _, ok := ac.mutation.Primary(); !ok {
v := attachment.DefaultPrimary
ac.mutation.SetPrimary(v)
}
if _, ok := ac.mutation.ID(); !ok { if _, ok := ac.mutation.ID(); !ok {
v := attachment.DefaultID() v := attachment.DefaultID()
ac.mutation.SetID(v) ac.mutation.SetID(v)
@ -188,9 +170,6 @@ func (ac *AttachmentCreate) check() error {
return &ValidationError{Name: "type", err: fmt.Errorf(`ent: validator failed for field "Attachment.type": %w`, err)} return &ValidationError{Name: "type", err: fmt.Errorf(`ent: validator failed for field "Attachment.type": %w`, err)}
} }
} }
if _, ok := ac.mutation.Primary(); !ok {
return &ValidationError{Name: "primary", err: errors.New(`ent: missing required field "Attachment.primary"`)}
}
if _, ok := ac.mutation.ItemID(); !ok { if _, ok := ac.mutation.ItemID(); !ok {
return &ValidationError{Name: "item", err: errors.New(`ent: missing required edge "Attachment.item"`)} return &ValidationError{Name: "item", err: errors.New(`ent: missing required edge "Attachment.item"`)}
} }
@ -244,10 +223,6 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
_spec.SetField(attachment.FieldType, field.TypeEnum, value) _spec.SetField(attachment.FieldType, field.TypeEnum, value)
_node.Type = value _node.Type = value
} }
if value, ok := ac.mutation.Primary(); ok {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
_node.Primary = value
}
if nodes := ac.mutation.ItemIDs(); len(nodes) > 0 { if nodes := ac.mutation.ItemIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,
@ -256,7 +231,10 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
Columns: []string{attachment.ItemColumn}, Columns: []string{attachment.ItemColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -273,7 +251,10 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
Columns: []string{attachment.DocumentColumn}, Columns: []string{attachment.DocumentColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -288,15 +269,11 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
// AttachmentCreateBulk is the builder for creating many Attachment entities in bulk. // AttachmentCreateBulk is the builder for creating many Attachment entities in bulk.
type AttachmentCreateBulk struct { type AttachmentCreateBulk struct {
config config
err error
builders []*AttachmentCreate builders []*AttachmentCreate
} }
// Save creates the Attachment entities in the database. // Save creates the Attachment entities in the database.
func (acb *AttachmentCreateBulk) Save(ctx context.Context) ([]*Attachment, error) { func (acb *AttachmentCreateBulk) Save(ctx context.Context) ([]*Attachment, error) {
if acb.err != nil {
return nil, acb.err
}
specs := make([]*sqlgraph.CreateSpec, len(acb.builders)) specs := make([]*sqlgraph.CreateSpec, len(acb.builders))
nodes := make([]*Attachment, len(acb.builders)) nodes := make([]*Attachment, len(acb.builders))
mutators := make([]Mutator, len(acb.builders)) mutators := make([]Mutator, len(acb.builders))
@ -313,8 +290,8 @@ func (acb *AttachmentCreateBulk) Save(ctx context.Context) ([]*Attachment, error
return nil, err return nil, err
} }
builder.mutation = mutation builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec() nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 { if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, acb.builders[i+1].mutation) _, err = mutators[i+1].Mutate(root, acb.builders[i+1].mutation)
} else { } else {

View file

@ -27,7 +27,7 @@ func (ad *AttachmentDelete) Where(ps ...predicate.Attachment) *AttachmentDelete
// Exec executes the deletion query and returns how many vertices were deleted. // Exec executes the deletion query and returns how many vertices were deleted.
func (ad *AttachmentDelete) Exec(ctx context.Context) (int, error) { func (ad *AttachmentDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, ad.sqlExec, ad.mutation, ad.hooks) return withHooks[int, AttachmentMutation](ctx, ad.sqlExec, ad.mutation, ad.hooks)
} }
// ExecX is like Exec, but panics if an error occurs. // ExecX is like Exec, but panics if an error occurs.

View file

@ -21,7 +21,7 @@ import (
type AttachmentQuery struct { type AttachmentQuery struct {
config config
ctx *QueryContext ctx *QueryContext
order []attachment.OrderOption order []OrderFunc
inters []Interceptor inters []Interceptor
predicates []predicate.Attachment predicates []predicate.Attachment
withItem *ItemQuery withItem *ItemQuery
@ -58,7 +58,7 @@ func (aq *AttachmentQuery) Unique(unique bool) *AttachmentQuery {
} }
// Order specifies how the records should be ordered. // Order specifies how the records should be ordered.
func (aq *AttachmentQuery) Order(o ...attachment.OrderOption) *AttachmentQuery { func (aq *AttachmentQuery) Order(o ...OrderFunc) *AttachmentQuery {
aq.order = append(aq.order, o...) aq.order = append(aq.order, o...)
return aq return aq
} }
@ -296,7 +296,7 @@ func (aq *AttachmentQuery) Clone() *AttachmentQuery {
return &AttachmentQuery{ return &AttachmentQuery{
config: aq.config, config: aq.config,
ctx: aq.ctx.Clone(), ctx: aq.ctx.Clone(),
order: append([]attachment.OrderOption{}, aq.order...), order: append([]OrderFunc{}, aq.order...),
inters: append([]Interceptor{}, aq.inters...), inters: append([]Interceptor{}, aq.inters...),
predicates: append([]predicate.Attachment{}, aq.predicates...), predicates: append([]predicate.Attachment{}, aq.predicates...),
withItem: aq.withItem.Clone(), withItem: aq.withItem.Clone(),

View file

@ -51,20 +51,6 @@ func (au *AttachmentUpdate) SetNillableType(a *attachment.Type) *AttachmentUpdat
return au return au
} }
// SetPrimary sets the "primary" field.
func (au *AttachmentUpdate) SetPrimary(b bool) *AttachmentUpdate {
au.mutation.SetPrimary(b)
return au
}
// SetNillablePrimary sets the "primary" field if the given value is not nil.
func (au *AttachmentUpdate) SetNillablePrimary(b *bool) *AttachmentUpdate {
if b != nil {
au.SetPrimary(*b)
}
return au
}
// SetItemID sets the "item" edge to the Item entity by ID. // SetItemID sets the "item" edge to the Item entity by ID.
func (au *AttachmentUpdate) SetItemID(id uuid.UUID) *AttachmentUpdate { func (au *AttachmentUpdate) SetItemID(id uuid.UUID) *AttachmentUpdate {
au.mutation.SetItemID(id) au.mutation.SetItemID(id)
@ -107,7 +93,7 @@ func (au *AttachmentUpdate) ClearDocument() *AttachmentUpdate {
// Save executes the query and returns the number of nodes affected by the update operation. // Save executes the query and returns the number of nodes affected by the update operation.
func (au *AttachmentUpdate) Save(ctx context.Context) (int, error) { func (au *AttachmentUpdate) Save(ctx context.Context) (int, error) {
au.defaults() au.defaults()
return withHooks(ctx, au.sqlSave, au.mutation, au.hooks) return withHooks[int, AttachmentMutation](ctx, au.sqlSave, au.mutation, au.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -174,9 +160,6 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := au.mutation.GetType(); ok { if value, ok := au.mutation.GetType(); ok {
_spec.SetField(attachment.FieldType, field.TypeEnum, value) _spec.SetField(attachment.FieldType, field.TypeEnum, value)
} }
if value, ok := au.mutation.Primary(); ok {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
}
if au.mutation.ItemCleared() { if au.mutation.ItemCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,
@ -185,7 +168,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.ItemColumn}, Columns: []string{attachment.ItemColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -198,7 +184,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.ItemColumn}, Columns: []string{attachment.ItemColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -214,7 +203,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.DocumentColumn}, Columns: []string{attachment.DocumentColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -227,7 +219,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.DocumentColumn}, Columns: []string{attachment.DocumentColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -275,20 +270,6 @@ func (auo *AttachmentUpdateOne) SetNillableType(a *attachment.Type) *AttachmentU
return auo return auo
} }
// SetPrimary sets the "primary" field.
func (auo *AttachmentUpdateOne) SetPrimary(b bool) *AttachmentUpdateOne {
auo.mutation.SetPrimary(b)
return auo
}
// SetNillablePrimary sets the "primary" field if the given value is not nil.
func (auo *AttachmentUpdateOne) SetNillablePrimary(b *bool) *AttachmentUpdateOne {
if b != nil {
auo.SetPrimary(*b)
}
return auo
}
// SetItemID sets the "item" edge to the Item entity by ID. // SetItemID sets the "item" edge to the Item entity by ID.
func (auo *AttachmentUpdateOne) SetItemID(id uuid.UUID) *AttachmentUpdateOne { func (auo *AttachmentUpdateOne) SetItemID(id uuid.UUID) *AttachmentUpdateOne {
auo.mutation.SetItemID(id) auo.mutation.SetItemID(id)
@ -344,7 +325,7 @@ func (auo *AttachmentUpdateOne) Select(field string, fields ...string) *Attachme
// Save executes the query and returns the updated Attachment entity. // Save executes the query and returns the updated Attachment entity.
func (auo *AttachmentUpdateOne) Save(ctx context.Context) (*Attachment, error) { func (auo *AttachmentUpdateOne) Save(ctx context.Context) (*Attachment, error) {
auo.defaults() auo.defaults()
return withHooks(ctx, auo.sqlSave, auo.mutation, auo.hooks) return withHooks[*Attachment, AttachmentMutation](ctx, auo.sqlSave, auo.mutation, auo.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -428,9 +409,6 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
if value, ok := auo.mutation.GetType(); ok { if value, ok := auo.mutation.GetType(); ok {
_spec.SetField(attachment.FieldType, field.TypeEnum, value) _spec.SetField(attachment.FieldType, field.TypeEnum, value)
} }
if value, ok := auo.mutation.Primary(); ok {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
}
if auo.mutation.ItemCleared() { if auo.mutation.ItemCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,
@ -439,7 +417,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.ItemColumn}, Columns: []string{attachment.ItemColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -452,7 +433,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.ItemColumn}, Columns: []string{attachment.ItemColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -468,7 +452,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.DocumentColumn}, Columns: []string{attachment.DocumentColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -481,7 +468,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.DocumentColumn}, Columns: []string{attachment.DocumentColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
@ -24,7 +23,6 @@ type AuthRoles struct {
// The values are being populated by the AuthRolesQuery when eager-loading is set. // The values are being populated by the AuthRolesQuery when eager-loading is set.
Edges AuthRolesEdges `json:"edges"` Edges AuthRolesEdges `json:"edges"`
auth_tokens_roles *uuid.UUID auth_tokens_roles *uuid.UUID
selectValues sql.SelectValues
} }
// AuthRolesEdges holds the relations/edges for other nodes in the graph. // AuthRolesEdges holds the relations/edges for other nodes in the graph.
@ -61,7 +59,7 @@ func (*AuthRoles) scanValues(columns []string) ([]any, error) {
case authroles.ForeignKeys[0]: // auth_tokens_roles case authroles.ForeignKeys[0]: // auth_tokens_roles
values[i] = &sql.NullScanner{S: new(uuid.UUID)} values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default: default:
values[i] = new(sql.UnknownType) return nil, fmt.Errorf("unexpected column %q for type AuthRoles", columns[i])
} }
} }
return values, nil return values, nil
@ -94,19 +92,11 @@ func (ar *AuthRoles) assignValues(columns []string, values []any) error {
ar.auth_tokens_roles = new(uuid.UUID) ar.auth_tokens_roles = new(uuid.UUID)
*ar.auth_tokens_roles = *value.S.(*uuid.UUID) *ar.auth_tokens_roles = *value.S.(*uuid.UUID)
} }
default:
ar.selectValues.Set(columns[i], values[i])
} }
} }
return nil return nil
} }
// Value returns the ent.Value that was dynamically selected and assigned to the AuthRoles.
// This includes values selected through modifiers, order, etc.
func (ar *AuthRoles) Value(name string) (ent.Value, error) {
return ar.selectValues.Get(name)
}
// QueryToken queries the "token" edge of the AuthRoles entity. // QueryToken queries the "token" edge of the AuthRoles entity.
func (ar *AuthRoles) QueryToken() *AuthTokensQuery { func (ar *AuthRoles) QueryToken() *AuthTokensQuery {
return NewAuthRolesClient(ar.config).QueryToken(ar) return NewAuthRolesClient(ar.config).QueryToken(ar)

View file

@ -4,9 +4,6 @@ package authroles
import ( import (
"fmt" "fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
) )
const ( const (
@ -82,30 +79,3 @@ func RoleValidator(r Role) error {
return fmt.Errorf("authroles: invalid enum value for role field: %q", r) return fmt.Errorf("authroles: invalid enum value for role field: %q", r)
} }
} }
// OrderOption defines the ordering options for the AuthRoles queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByRole orders the results by the role field.
func ByRole(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRole, opts...).ToFunc()
}
// ByTokenField orders the results by token field.
func ByTokenField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newTokenStep(), sql.OrderByField(field, opts...))
}
}
func newTokenStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(TokenInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2O, true, TokenTable, TokenColumn),
)
}

View file

@ -87,7 +87,11 @@ func HasToken() predicate.AuthRoles {
// HasTokenWith applies the HasEdge predicate on the "token" edge with a given conditions (other predicates). // HasTokenWith applies the HasEdge predicate on the "token" edge with a given conditions (other predicates).
func HasTokenWith(preds ...predicate.AuthTokens) predicate.AuthRoles { func HasTokenWith(preds ...predicate.AuthTokens) predicate.AuthRoles {
return predicate.AuthRoles(func(s *sql.Selector) { return predicate.AuthRoles(func(s *sql.Selector) {
step := newTokenStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(TokenInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2O, true, TokenTable, TokenColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -98,15 +102,32 @@ func HasTokenWith(preds ...predicate.AuthTokens) predicate.AuthRoles {
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.AuthRoles) predicate.AuthRoles { func And(predicates ...predicate.AuthRoles) predicate.AuthRoles {
return predicate.AuthRoles(sql.AndPredicates(predicates...)) return predicate.AuthRoles(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for _, p := range predicates {
p(s1)
}
s.Where(s1.P())
})
} }
// Or groups predicates with the OR operator between them. // Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.AuthRoles) predicate.AuthRoles { func Or(predicates ...predicate.AuthRoles) predicate.AuthRoles {
return predicate.AuthRoles(sql.OrPredicates(predicates...)) return predicate.AuthRoles(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for i, p := range predicates {
if i > 0 {
s1.Or()
}
p(s1)
}
s.Where(s1.P())
})
} }
// Not applies the not operator on the given predicate. // Not applies the not operator on the given predicate.
func Not(p predicate.AuthRoles) predicate.AuthRoles { func Not(p predicate.AuthRoles) predicate.AuthRoles {
return predicate.AuthRoles(sql.NotPredicates(p)) return predicate.AuthRoles(func(s *sql.Selector) {
p(s.Not())
})
} }

View file

@ -62,7 +62,7 @@ func (arc *AuthRolesCreate) Mutation() *AuthRolesMutation {
// Save creates the AuthRoles in the database. // Save creates the AuthRoles in the database.
func (arc *AuthRolesCreate) Save(ctx context.Context) (*AuthRoles, error) { func (arc *AuthRolesCreate) Save(ctx context.Context) (*AuthRoles, error) {
arc.defaults() arc.defaults()
return withHooks(ctx, arc.sqlSave, arc.mutation, arc.hooks) return withHooks[*AuthRoles, AuthRolesMutation](ctx, arc.sqlSave, arc.mutation, arc.hooks)
} }
// SaveX calls Save and panics if Save returns an error. // SaveX calls Save and panics if Save returns an error.
@ -143,7 +143,10 @@ func (arc *AuthRolesCreate) createSpec() (*AuthRoles, *sqlgraph.CreateSpec) {
Columns: []string{authroles.TokenColumn}, Columns: []string{authroles.TokenColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -158,15 +161,11 @@ func (arc *AuthRolesCreate) createSpec() (*AuthRoles, *sqlgraph.CreateSpec) {
// AuthRolesCreateBulk is the builder for creating many AuthRoles entities in bulk. // AuthRolesCreateBulk is the builder for creating many AuthRoles entities in bulk.
type AuthRolesCreateBulk struct { type AuthRolesCreateBulk struct {
config config
err error
builders []*AuthRolesCreate builders []*AuthRolesCreate
} }
// Save creates the AuthRoles entities in the database. // Save creates the AuthRoles entities in the database.
func (arcb *AuthRolesCreateBulk) Save(ctx context.Context) ([]*AuthRoles, error) { func (arcb *AuthRolesCreateBulk) Save(ctx context.Context) ([]*AuthRoles, error) {
if arcb.err != nil {
return nil, arcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(arcb.builders)) specs := make([]*sqlgraph.CreateSpec, len(arcb.builders))
nodes := make([]*AuthRoles, len(arcb.builders)) nodes := make([]*AuthRoles, len(arcb.builders))
mutators := make([]Mutator, len(arcb.builders)) mutators := make([]Mutator, len(arcb.builders))
@ -183,8 +182,8 @@ func (arcb *AuthRolesCreateBulk) Save(ctx context.Context) ([]*AuthRoles, error)
return nil, err return nil, err
} }
builder.mutation = mutation builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec() nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 { if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, arcb.builders[i+1].mutation) _, err = mutators[i+1].Mutate(root, arcb.builders[i+1].mutation)
} else { } else {

View file

@ -27,7 +27,7 @@ func (ard *AuthRolesDelete) Where(ps ...predicate.AuthRoles) *AuthRolesDelete {
// Exec executes the deletion query and returns how many vertices were deleted. // Exec executes the deletion query and returns how many vertices were deleted.
func (ard *AuthRolesDelete) Exec(ctx context.Context) (int, error) { func (ard *AuthRolesDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, ard.sqlExec, ard.mutation, ard.hooks) return withHooks[int, AuthRolesMutation](ctx, ard.sqlExec, ard.mutation, ard.hooks)
} }
// ExecX is like Exec, but panics if an error occurs. // ExecX is like Exec, but panics if an error occurs.

View file

@ -20,7 +20,7 @@ import (
type AuthRolesQuery struct { type AuthRolesQuery struct {
config config
ctx *QueryContext ctx *QueryContext
order []authroles.OrderOption order []OrderFunc
inters []Interceptor inters []Interceptor
predicates []predicate.AuthRoles predicates []predicate.AuthRoles
withToken *AuthTokensQuery withToken *AuthTokensQuery
@ -56,7 +56,7 @@ func (arq *AuthRolesQuery) Unique(unique bool) *AuthRolesQuery {
} }
// Order specifies how the records should be ordered. // Order specifies how the records should be ordered.
func (arq *AuthRolesQuery) Order(o ...authroles.OrderOption) *AuthRolesQuery { func (arq *AuthRolesQuery) Order(o ...OrderFunc) *AuthRolesQuery {
arq.order = append(arq.order, o...) arq.order = append(arq.order, o...)
return arq return arq
} }
@ -272,7 +272,7 @@ func (arq *AuthRolesQuery) Clone() *AuthRolesQuery {
return &AuthRolesQuery{ return &AuthRolesQuery{
config: arq.config, config: arq.config,
ctx: arq.ctx.Clone(), ctx: arq.ctx.Clone(),
order: append([]authroles.OrderOption{}, arq.order...), order: append([]OrderFunc{}, arq.order...),
inters: append([]Interceptor{}, arq.inters...), inters: append([]Interceptor{}, arq.inters...),
predicates: append([]predicate.AuthRoles{}, arq.predicates...), predicates: append([]predicate.AuthRoles{}, arq.predicates...),
withToken: arq.withToken.Clone(), withToken: arq.withToken.Clone(),

View file

@ -75,7 +75,7 @@ func (aru *AuthRolesUpdate) ClearToken() *AuthRolesUpdate {
// Save executes the query and returns the number of nodes affected by the update operation. // Save executes the query and returns the number of nodes affected by the update operation.
func (aru *AuthRolesUpdate) Save(ctx context.Context) (int, error) { func (aru *AuthRolesUpdate) Save(ctx context.Context) (int, error) {
return withHooks(ctx, aru.sqlSave, aru.mutation, aru.hooks) return withHooks[int, AuthRolesMutation](ctx, aru.sqlSave, aru.mutation, aru.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -133,7 +133,10 @@ func (aru *AuthRolesUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authroles.TokenColumn}, Columns: []string{authroles.TokenColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -146,7 +149,10 @@ func (aru *AuthRolesUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authroles.TokenColumn}, Columns: []string{authroles.TokenColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -233,7 +239,7 @@ func (aruo *AuthRolesUpdateOne) Select(field string, fields ...string) *AuthRole
// Save executes the query and returns the updated AuthRoles entity. // Save executes the query and returns the updated AuthRoles entity.
func (aruo *AuthRolesUpdateOne) Save(ctx context.Context) (*AuthRoles, error) { func (aruo *AuthRolesUpdateOne) Save(ctx context.Context) (*AuthRoles, error) {
return withHooks(ctx, aruo.sqlSave, aruo.mutation, aruo.hooks) return withHooks[*AuthRoles, AuthRolesMutation](ctx, aruo.sqlSave, aruo.mutation, aruo.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -308,7 +314,10 @@ func (aruo *AuthRolesUpdateOne) sqlSave(ctx context.Context) (_node *AuthRoles,
Columns: []string{authroles.TokenColumn}, Columns: []string{authroles.TokenColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -321,7 +330,10 @@ func (aruo *AuthRolesUpdateOne) sqlSave(ctx context.Context) (_node *AuthRoles,
Columns: []string{authroles.TokenColumn}, Columns: []string{authroles.TokenColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {

View file

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
@ -32,7 +31,6 @@ type AuthTokens struct {
// The values are being populated by the AuthTokensQuery when eager-loading is set. // The values are being populated by the AuthTokensQuery when eager-loading is set.
Edges AuthTokensEdges `json:"edges"` Edges AuthTokensEdges `json:"edges"`
user_auth_tokens *uuid.UUID user_auth_tokens *uuid.UUID
selectValues sql.SelectValues
} }
// AuthTokensEdges holds the relations/edges for other nodes in the graph. // AuthTokensEdges holds the relations/edges for other nodes in the graph.
@ -86,7 +84,7 @@ func (*AuthTokens) scanValues(columns []string) ([]any, error) {
case authtokens.ForeignKeys[0]: // user_auth_tokens case authtokens.ForeignKeys[0]: // user_auth_tokens
values[i] = &sql.NullScanner{S: new(uuid.UUID)} values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default: default:
values[i] = new(sql.UnknownType) return nil, fmt.Errorf("unexpected column %q for type AuthTokens", columns[i])
} }
} }
return values, nil return values, nil
@ -137,19 +135,11 @@ func (at *AuthTokens) assignValues(columns []string, values []any) error {
at.user_auth_tokens = new(uuid.UUID) at.user_auth_tokens = new(uuid.UUID)
*at.user_auth_tokens = *value.S.(*uuid.UUID) *at.user_auth_tokens = *value.S.(*uuid.UUID)
} }
default:
at.selectValues.Set(columns[i], values[i])
} }
} }
return nil return nil
} }
// Value returns the ent.Value that was dynamically selected and assigned to the AuthTokens.
// This includes values selected through modifiers, order, etc.
func (at *AuthTokens) Value(name string) (ent.Value, error) {
return at.selectValues.Get(name)
}
// QueryUser queries the "user" edge of the AuthTokens entity. // QueryUser queries the "user" edge of the AuthTokens entity.
func (at *AuthTokens) QueryUser() *UserQuery { func (at *AuthTokens) QueryUser() *UserQuery {
return NewAuthTokensClient(at.config).QueryUser(at) return NewAuthTokensClient(at.config).QueryUser(at)

View file

@ -5,8 +5,6 @@ package authtokens
import ( import (
"time" "time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -87,54 +85,3 @@ var (
// DefaultID holds the default value on creation for the "id" field. // DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID DefaultID func() uuid.UUID
) )
// OrderOption defines the ordering options for the AuthTokens queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByExpiresAt orders the results by the expires_at field.
func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
}
// ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
}
}
// ByRolesField orders the results by roles field.
func ByRolesField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newRolesStep(), sql.OrderByField(field, opts...))
}
}
func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
}
func newRolesStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(RolesInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2O, false, RolesTable, RolesColumn),
)
}

View file

@ -250,7 +250,11 @@ func HasUser() predicate.AuthTokens {
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). // HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
func HasUserWith(preds ...predicate.User) predicate.AuthTokens { func HasUserWith(preds ...predicate.User) predicate.AuthTokens {
return predicate.AuthTokens(func(s *sql.Selector) { return predicate.AuthTokens(func(s *sql.Selector) {
step := newUserStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -273,7 +277,11 @@ func HasRoles() predicate.AuthTokens {
// HasRolesWith applies the HasEdge predicate on the "roles" edge with a given conditions (other predicates). // HasRolesWith applies the HasEdge predicate on the "roles" edge with a given conditions (other predicates).
func HasRolesWith(preds ...predicate.AuthRoles) predicate.AuthTokens { func HasRolesWith(preds ...predicate.AuthRoles) predicate.AuthTokens {
return predicate.AuthTokens(func(s *sql.Selector) { return predicate.AuthTokens(func(s *sql.Selector) {
step := newRolesStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(RolesInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2O, false, RolesTable, RolesColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -284,15 +292,32 @@ func HasRolesWith(preds ...predicate.AuthRoles) predicate.AuthTokens {
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.AuthTokens) predicate.AuthTokens { func And(predicates ...predicate.AuthTokens) predicate.AuthTokens {
return predicate.AuthTokens(sql.AndPredicates(predicates...)) return predicate.AuthTokens(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for _, p := range predicates {
p(s1)
}
s.Where(s1.P())
})
} }
// Or groups predicates with the OR operator between them. // Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.AuthTokens) predicate.AuthTokens { func Or(predicates ...predicate.AuthTokens) predicate.AuthTokens {
return predicate.AuthTokens(sql.OrPredicates(predicates...)) return predicate.AuthTokens(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for i, p := range predicates {
if i > 0 {
s1.Or()
}
p(s1)
}
s.Where(s1.P())
})
} }
// Not applies the not operator on the given predicate. // Not applies the not operator on the given predicate.
func Not(p predicate.AuthTokens) predicate.AuthTokens { func Not(p predicate.AuthTokens) predicate.AuthTokens {
return predicate.AuthTokens(sql.NotPredicates(p)) return predicate.AuthTokens(func(s *sql.Selector) {
p(s.Not())
})
} }

View file

@ -131,7 +131,7 @@ func (atc *AuthTokensCreate) Mutation() *AuthTokensMutation {
// Save creates the AuthTokens in the database. // Save creates the AuthTokens in the database.
func (atc *AuthTokensCreate) Save(ctx context.Context) (*AuthTokens, error) { func (atc *AuthTokensCreate) Save(ctx context.Context) (*AuthTokens, error) {
atc.defaults() atc.defaults()
return withHooks(ctx, atc.sqlSave, atc.mutation, atc.hooks) return withHooks[*AuthTokens, AuthTokensMutation](ctx, atc.sqlSave, atc.mutation, atc.hooks)
} }
// SaveX calls Save and panics if Save returns an error. // SaveX calls Save and panics if Save returns an error.
@ -249,7 +249,10 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
Columns: []string{authtokens.UserColumn}, Columns: []string{authtokens.UserColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -266,7 +269,10 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
Columns: []string{authtokens.RolesColumn}, Columns: []string{authtokens.RolesColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -280,15 +286,11 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
// AuthTokensCreateBulk is the builder for creating many AuthTokens entities in bulk. // AuthTokensCreateBulk is the builder for creating many AuthTokens entities in bulk.
type AuthTokensCreateBulk struct { type AuthTokensCreateBulk struct {
config config
err error
builders []*AuthTokensCreate builders []*AuthTokensCreate
} }
// Save creates the AuthTokens entities in the database. // Save creates the AuthTokens entities in the database.
func (atcb *AuthTokensCreateBulk) Save(ctx context.Context) ([]*AuthTokens, error) { func (atcb *AuthTokensCreateBulk) Save(ctx context.Context) ([]*AuthTokens, error) {
if atcb.err != nil {
return nil, atcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(atcb.builders)) specs := make([]*sqlgraph.CreateSpec, len(atcb.builders))
nodes := make([]*AuthTokens, len(atcb.builders)) nodes := make([]*AuthTokens, len(atcb.builders))
mutators := make([]Mutator, len(atcb.builders)) mutators := make([]Mutator, len(atcb.builders))
@ -305,8 +307,8 @@ func (atcb *AuthTokensCreateBulk) Save(ctx context.Context) ([]*AuthTokens, erro
return nil, err return nil, err
} }
builder.mutation = mutation builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec() nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 { if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, atcb.builders[i+1].mutation) _, err = mutators[i+1].Mutate(root, atcb.builders[i+1].mutation)
} else { } else {

View file

@ -27,7 +27,7 @@ func (atd *AuthTokensDelete) Where(ps ...predicate.AuthTokens) *AuthTokensDelete
// Exec executes the deletion query and returns how many vertices were deleted. // Exec executes the deletion query and returns how many vertices were deleted.
func (atd *AuthTokensDelete) Exec(ctx context.Context) (int, error) { func (atd *AuthTokensDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, atd.sqlExec, atd.mutation, atd.hooks) return withHooks[int, AuthTokensMutation](ctx, atd.sqlExec, atd.mutation, atd.hooks)
} }
// ExecX is like Exec, but panics if an error occurs. // ExecX is like Exec, but panics if an error occurs.

View file

@ -22,7 +22,7 @@ import (
type AuthTokensQuery struct { type AuthTokensQuery struct {
config config
ctx *QueryContext ctx *QueryContext
order []authtokens.OrderOption order []OrderFunc
inters []Interceptor inters []Interceptor
predicates []predicate.AuthTokens predicates []predicate.AuthTokens
withUser *UserQuery withUser *UserQuery
@ -59,7 +59,7 @@ func (atq *AuthTokensQuery) Unique(unique bool) *AuthTokensQuery {
} }
// Order specifies how the records should be ordered. // Order specifies how the records should be ordered.
func (atq *AuthTokensQuery) Order(o ...authtokens.OrderOption) *AuthTokensQuery { func (atq *AuthTokensQuery) Order(o ...OrderFunc) *AuthTokensQuery {
atq.order = append(atq.order, o...) atq.order = append(atq.order, o...)
return atq return atq
} }
@ -297,7 +297,7 @@ func (atq *AuthTokensQuery) Clone() *AuthTokensQuery {
return &AuthTokensQuery{ return &AuthTokensQuery{
config: atq.config, config: atq.config,
ctx: atq.ctx.Clone(), ctx: atq.ctx.Clone(),
order: append([]authtokens.OrderOption{}, atq.order...), order: append([]OrderFunc{}, atq.order...),
inters: append([]Interceptor{}, atq.inters...), inters: append([]Interceptor{}, atq.inters...),
predicates: append([]predicate.AuthTokens{}, atq.predicates...), predicates: append([]predicate.AuthTokens{}, atq.predicates...),
withUser: atq.withUser.Clone(), withUser: atq.withUser.Clone(),
@ -494,7 +494,7 @@ func (atq *AuthTokensQuery) loadRoles(ctx context.Context, query *AuthRolesQuery
} }
query.withFKs = true query.withFKs = true
query.Where(predicate.AuthRoles(func(s *sql.Selector) { query.Where(predicate.AuthRoles(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(authtokens.RolesColumn), fks...)) s.Where(sql.InValues(authtokens.RolesColumn, fks...))
})) }))
neighbors, err := query.All(ctx) neighbors, err := query.All(ctx)
if err != nil { if err != nil {
@ -507,7 +507,7 @@ func (atq *AuthTokensQuery) loadRoles(ctx context.Context, query *AuthRolesQuery
} }
node, ok := nodeids[*fk] node, ok := nodeids[*fk]
if !ok { if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "auth_tokens_roles" returned %v for node %v`, *fk, n.ID) return fmt.Errorf(`unexpected foreign-key "auth_tokens_roles" returned %v for node %v`, *fk, n.ID)
} }
assign(node, n) assign(node, n)
} }

View file

@ -115,7 +115,7 @@ func (atu *AuthTokensUpdate) ClearRoles() *AuthTokensUpdate {
// Save executes the query and returns the number of nodes affected by the update operation. // Save executes the query and returns the number of nodes affected by the update operation.
func (atu *AuthTokensUpdate) Save(ctx context.Context) (int, error) { func (atu *AuthTokensUpdate) Save(ctx context.Context) (int, error) {
atu.defaults() atu.defaults()
return withHooks(ctx, atu.sqlSave, atu.mutation, atu.hooks) return withHooks[int, AuthTokensMutation](ctx, atu.sqlSave, atu.mutation, atu.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -174,7 +174,10 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.UserColumn}, Columns: []string{authtokens.UserColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -187,7 +190,10 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.UserColumn}, Columns: []string{authtokens.UserColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -203,7 +209,10 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.RolesColumn}, Columns: []string{authtokens.RolesColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -216,7 +225,10 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.RolesColumn}, Columns: []string{authtokens.RolesColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -341,7 +353,7 @@ func (atuo *AuthTokensUpdateOne) Select(field string, fields ...string) *AuthTok
// Save executes the query and returns the updated AuthTokens entity. // Save executes the query and returns the updated AuthTokens entity.
func (atuo *AuthTokensUpdateOne) Save(ctx context.Context) (*AuthTokens, error) { func (atuo *AuthTokensUpdateOne) Save(ctx context.Context) (*AuthTokens, error) {
atuo.defaults() atuo.defaults()
return withHooks(ctx, atuo.sqlSave, atuo.mutation, atuo.hooks) return withHooks[*AuthTokens, AuthTokensMutation](ctx, atuo.sqlSave, atuo.mutation, atuo.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -417,7 +429,10 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.UserColumn}, Columns: []string{authtokens.UserColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -430,7 +445,10 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.UserColumn}, Columns: []string{authtokens.UserColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -446,7 +464,10 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.RolesColumn}, Columns: []string{authtokens.RolesColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -459,7 +480,10 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.RolesColumn}, Columns: []string{authtokens.RolesColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeInt,
Column: authroles.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {

View file

@ -7,15 +7,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"reflect"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/migrate" "github.com/hay-kot/homebox/backend/internal/data/ent/migrate"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
@ -29,6 +24,10 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/maintenanceentry" "github.com/hay-kot/homebox/backend/internal/data/ent/maintenanceentry"
"github.com/hay-kot/homebox/backend/internal/data/ent/notifier" "github.com/hay-kot/homebox/backend/internal/data/ent/notifier"
"github.com/hay-kot/homebox/backend/internal/data/ent/user" "github.com/hay-kot/homebox/backend/internal/data/ent/user"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
) )
// Client is the client that holds all ent builders. // Client is the client that holds all ent builders.
@ -66,7 +65,9 @@ type Client struct {
// NewClient creates a new client configured with the given options. // NewClient creates a new client configured with the given options.
func NewClient(opts ...Option) *Client { func NewClient(opts ...Option) *Client {
client := &Client{config: newConfig(opts...)} cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}}
cfg.options(opts...)
client := &Client{config: cfg}
client.init() client.init()
return client return client
} }
@ -88,62 +89,6 @@ func (c *Client) init() {
c.User = NewUserClient(c.config) c.User = NewUserClient(c.config)
} }
type (
// config is the configuration for the client and its builder.
config struct {
// driver used for executing database requests.
driver dialect.Driver
// debug enable a debug logging.
debug bool
// log used for logging on debug mode.
log func(...any)
// hooks to execute on mutations.
hooks *hooks
// interceptors to execute on queries.
inters *inters
}
// Option function to configure the client.
Option func(*config)
)
// newConfig creates a new config for the client.
func newConfig(opts ...Option) config {
cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}}
cfg.options(opts...)
return cfg
}
// options applies the options on the config object.
func (c *config) options(opts ...Option) {
for _, opt := range opts {
opt(c)
}
if c.debug {
c.driver = dialect.Debug(c.driver, c.log)
}
}
// Debug enables debug logging on the ent.Driver.
func Debug() Option {
return func(c *config) {
c.debug = true
}
}
// Log sets the logging function for debug mode.
func Log(fn func(...any)) Option {
return func(c *config) {
c.log = fn
}
}
// Driver configures the client driver.
func Driver(driver dialect.Driver) Option {
return func(c *config) {
c.driver = driver
}
}
// Open opens a database/sql.DB specified by the driver name and // Open opens a database/sql.DB specified by the driver name and
// the data source name, and returns a new client attached to it. // the data source name, and returns a new client attached to it.
// Optional parameters can be added for configuring the client. // Optional parameters can be added for configuring the client.
@ -160,14 +105,11 @@ func Open(driverName, dataSourceName string, options ...Option) (*Client, error)
} }
} }
// ErrTxStarted is returned when trying to start a new transaction from a transactional client.
var ErrTxStarted = errors.New("ent: cannot start a transaction within a transaction")
// Tx returns a new transactional client. The provided context // Tx returns a new transactional client. The provided context
// is used until the transaction is committed or rolled back. // is used until the transaction is committed or rolled back.
func (c *Client) Tx(ctx context.Context) (*Tx, error) { func (c *Client) Tx(ctx context.Context) (*Tx, error) {
if _, ok := c.driver.(*txDriver); ok { if _, ok := c.driver.(*txDriver); ok {
return nil, ErrTxStarted return nil, errors.New("ent: cannot start a transaction within a transaction")
} }
tx, err := newTx(ctx, c.driver) tx, err := newTx(ctx, c.driver)
if err != nil { if err != nil {
@ -251,25 +193,37 @@ func (c *Client) Close() error {
// Use adds the mutation hooks to all the entity clients. // Use adds the mutation hooks to all the entity clients.
// In order to add hooks to a specific client, call: `client.Node.Use(...)`. // In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) { func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{ c.Attachment.Use(hooks...)
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, c.AuthRoles.Use(hooks...)
c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location, c.AuthTokens.Use(hooks...)
c.MaintenanceEntry, c.Notifier, c.User, c.Document.Use(hooks...)
} { c.Group.Use(hooks...)
n.Use(hooks...) c.GroupInvitationToken.Use(hooks...)
} c.Item.Use(hooks...)
c.ItemField.Use(hooks...)
c.Label.Use(hooks...)
c.Location.Use(hooks...)
c.MaintenanceEntry.Use(hooks...)
c.Notifier.Use(hooks...)
c.User.Use(hooks...)
} }
// Intercept adds the query interceptors to all the entity clients. // Intercept adds the query interceptors to all the entity clients.
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) { func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{ c.Attachment.Intercept(interceptors...)
c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, c.AuthRoles.Intercept(interceptors...)
c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location, c.AuthTokens.Intercept(interceptors...)
c.MaintenanceEntry, c.Notifier, c.User, c.Document.Intercept(interceptors...)
} { c.Group.Intercept(interceptors...)
n.Intercept(interceptors...) c.GroupInvitationToken.Intercept(interceptors...)
} c.Item.Intercept(interceptors...)
c.ItemField.Intercept(interceptors...)
c.Label.Intercept(interceptors...)
c.Location.Intercept(interceptors...)
c.MaintenanceEntry.Intercept(interceptors...)
c.Notifier.Intercept(interceptors...)
c.User.Intercept(interceptors...)
} }
// Mutate implements the ent.Mutator interface. // Mutate implements the ent.Mutator interface.
@ -339,21 +293,6 @@ func (c *AttachmentClient) CreateBulk(builders ...*AttachmentCreate) *Attachment
return &AttachmentCreateBulk{config: c.config, builders: builders} return &AttachmentCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *AttachmentClient) MapCreateBulk(slice any, setFunc func(*AttachmentCreate, int)) *AttachmentCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &AttachmentCreateBulk{err: fmt.Errorf("calling to AttachmentClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*AttachmentCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &AttachmentCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Attachment. // Update returns an update builder for Attachment.
func (c *AttachmentClient) Update() *AttachmentUpdate { func (c *AttachmentClient) Update() *AttachmentUpdate {
mutation := newAttachmentMutation(c.config, OpUpdate) mutation := newAttachmentMutation(c.config, OpUpdate)
@ -504,21 +443,6 @@ func (c *AuthRolesClient) CreateBulk(builders ...*AuthRolesCreate) *AuthRolesCre
return &AuthRolesCreateBulk{config: c.config, builders: builders} return &AuthRolesCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *AuthRolesClient) MapCreateBulk(slice any, setFunc func(*AuthRolesCreate, int)) *AuthRolesCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &AuthRolesCreateBulk{err: fmt.Errorf("calling to AuthRolesClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*AuthRolesCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &AuthRolesCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for AuthRoles. // Update returns an update builder for AuthRoles.
func (c *AuthRolesClient) Update() *AuthRolesUpdate { func (c *AuthRolesClient) Update() *AuthRolesUpdate {
mutation := newAuthRolesMutation(c.config, OpUpdate) mutation := newAuthRolesMutation(c.config, OpUpdate)
@ -653,21 +577,6 @@ func (c *AuthTokensClient) CreateBulk(builders ...*AuthTokensCreate) *AuthTokens
return &AuthTokensCreateBulk{config: c.config, builders: builders} return &AuthTokensCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *AuthTokensClient) MapCreateBulk(slice any, setFunc func(*AuthTokensCreate, int)) *AuthTokensCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &AuthTokensCreateBulk{err: fmt.Errorf("calling to AuthTokensClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*AuthTokensCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &AuthTokensCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for AuthTokens. // Update returns an update builder for AuthTokens.
func (c *AuthTokensClient) Update() *AuthTokensUpdate { func (c *AuthTokensClient) Update() *AuthTokensUpdate {
mutation := newAuthTokensMutation(c.config, OpUpdate) mutation := newAuthTokensMutation(c.config, OpUpdate)
@ -818,21 +727,6 @@ func (c *DocumentClient) CreateBulk(builders ...*DocumentCreate) *DocumentCreate
return &DocumentCreateBulk{config: c.config, builders: builders} return &DocumentCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *DocumentClient) MapCreateBulk(slice any, setFunc func(*DocumentCreate, int)) *DocumentCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &DocumentCreateBulk{err: fmt.Errorf("calling to DocumentClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*DocumentCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &DocumentCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Document. // Update returns an update builder for Document.
func (c *DocumentClient) Update() *DocumentUpdate { func (c *DocumentClient) Update() *DocumentUpdate {
mutation := newDocumentMutation(c.config, OpUpdate) mutation := newDocumentMutation(c.config, OpUpdate)
@ -983,21 +877,6 @@ func (c *GroupClient) CreateBulk(builders ...*GroupCreate) *GroupCreateBulk {
return &GroupCreateBulk{config: c.config, builders: builders} return &GroupCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *GroupClient) MapCreateBulk(slice any, setFunc func(*GroupCreate, int)) *GroupCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &GroupCreateBulk{err: fmt.Errorf("calling to GroupClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*GroupCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &GroupCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Group. // Update returns an update builder for Group.
func (c *GroupClient) Update() *GroupUpdate { func (c *GroupClient) Update() *GroupUpdate {
mutation := newGroupMutation(c.config, OpUpdate) mutation := newGroupMutation(c.config, OpUpdate)
@ -1228,21 +1107,6 @@ func (c *GroupInvitationTokenClient) CreateBulk(builders ...*GroupInvitationToke
return &GroupInvitationTokenCreateBulk{config: c.config, builders: builders} return &GroupInvitationTokenCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *GroupInvitationTokenClient) MapCreateBulk(slice any, setFunc func(*GroupInvitationTokenCreate, int)) *GroupInvitationTokenCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &GroupInvitationTokenCreateBulk{err: fmt.Errorf("calling to GroupInvitationTokenClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*GroupInvitationTokenCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &GroupInvitationTokenCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for GroupInvitationToken. // Update returns an update builder for GroupInvitationToken.
func (c *GroupInvitationTokenClient) Update() *GroupInvitationTokenUpdate { func (c *GroupInvitationTokenClient) Update() *GroupInvitationTokenUpdate {
mutation := newGroupInvitationTokenMutation(c.config, OpUpdate) mutation := newGroupInvitationTokenMutation(c.config, OpUpdate)
@ -1377,21 +1241,6 @@ func (c *ItemClient) CreateBulk(builders ...*ItemCreate) *ItemCreateBulk {
return &ItemCreateBulk{config: c.config, builders: builders} return &ItemCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *ItemClient) MapCreateBulk(slice any, setFunc func(*ItemCreate, int)) *ItemCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &ItemCreateBulk{err: fmt.Errorf("calling to ItemClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*ItemCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &ItemCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Item. // Update returns an update builder for Item.
func (c *ItemClient) Update() *ItemUpdate { func (c *ItemClient) Update() *ItemUpdate {
mutation := newItemMutation(c.config, OpUpdate) mutation := newItemMutation(c.config, OpUpdate)
@ -1638,21 +1487,6 @@ func (c *ItemFieldClient) CreateBulk(builders ...*ItemFieldCreate) *ItemFieldCre
return &ItemFieldCreateBulk{config: c.config, builders: builders} return &ItemFieldCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *ItemFieldClient) MapCreateBulk(slice any, setFunc func(*ItemFieldCreate, int)) *ItemFieldCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &ItemFieldCreateBulk{err: fmt.Errorf("calling to ItemFieldClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*ItemFieldCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &ItemFieldCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for ItemField. // Update returns an update builder for ItemField.
func (c *ItemFieldClient) Update() *ItemFieldUpdate { func (c *ItemFieldClient) Update() *ItemFieldUpdate {
mutation := newItemFieldMutation(c.config, OpUpdate) mutation := newItemFieldMutation(c.config, OpUpdate)
@ -1787,21 +1621,6 @@ func (c *LabelClient) CreateBulk(builders ...*LabelCreate) *LabelCreateBulk {
return &LabelCreateBulk{config: c.config, builders: builders} return &LabelCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *LabelClient) MapCreateBulk(slice any, setFunc func(*LabelCreate, int)) *LabelCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &LabelCreateBulk{err: fmt.Errorf("calling to LabelClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*LabelCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &LabelCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Label. // Update returns an update builder for Label.
func (c *LabelClient) Update() *LabelUpdate { func (c *LabelClient) Update() *LabelUpdate {
mutation := newLabelMutation(c.config, OpUpdate) mutation := newLabelMutation(c.config, OpUpdate)
@ -1952,21 +1771,6 @@ func (c *LocationClient) CreateBulk(builders ...*LocationCreate) *LocationCreate
return &LocationCreateBulk{config: c.config, builders: builders} return &LocationCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *LocationClient) MapCreateBulk(slice any, setFunc func(*LocationCreate, int)) *LocationCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &LocationCreateBulk{err: fmt.Errorf("calling to LocationClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*LocationCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &LocationCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Location. // Update returns an update builder for Location.
func (c *LocationClient) Update() *LocationUpdate { func (c *LocationClient) Update() *LocationUpdate {
mutation := newLocationMutation(c.config, OpUpdate) mutation := newLocationMutation(c.config, OpUpdate)
@ -2149,21 +1953,6 @@ func (c *MaintenanceEntryClient) CreateBulk(builders ...*MaintenanceEntryCreate)
return &MaintenanceEntryCreateBulk{config: c.config, builders: builders} return &MaintenanceEntryCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *MaintenanceEntryClient) MapCreateBulk(slice any, setFunc func(*MaintenanceEntryCreate, int)) *MaintenanceEntryCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &MaintenanceEntryCreateBulk{err: fmt.Errorf("calling to MaintenanceEntryClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*MaintenanceEntryCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &MaintenanceEntryCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for MaintenanceEntry. // Update returns an update builder for MaintenanceEntry.
func (c *MaintenanceEntryClient) Update() *MaintenanceEntryUpdate { func (c *MaintenanceEntryClient) Update() *MaintenanceEntryUpdate {
mutation := newMaintenanceEntryMutation(c.config, OpUpdate) mutation := newMaintenanceEntryMutation(c.config, OpUpdate)
@ -2298,21 +2087,6 @@ func (c *NotifierClient) CreateBulk(builders ...*NotifierCreate) *NotifierCreate
return &NotifierCreateBulk{config: c.config, builders: builders} return &NotifierCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *NotifierClient) MapCreateBulk(slice any, setFunc func(*NotifierCreate, int)) *NotifierCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &NotifierCreateBulk{err: fmt.Errorf("calling to NotifierClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*NotifierCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &NotifierCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for Notifier. // Update returns an update builder for Notifier.
func (c *NotifierClient) Update() *NotifierUpdate { func (c *NotifierClient) Update() *NotifierUpdate {
mutation := newNotifierMutation(c.config, OpUpdate) mutation := newNotifierMutation(c.config, OpUpdate)
@ -2463,21 +2237,6 @@ func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk {
return &UserCreateBulk{config: c.config, builders: builders} return &UserCreateBulk{config: c.config, builders: builders}
} }
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *UserClient) MapCreateBulk(slice any, setFunc func(*UserCreate, int)) *UserCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &UserCreateBulk{err: fmt.Errorf("calling to UserClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*UserCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &UserCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for User. // Update returns an update builder for User.
func (c *UserClient) Update() *UserUpdate { func (c *UserClient) Update() *UserUpdate {
mutation := newUserMutation(c.config, OpUpdate) mutation := newUserMutation(c.config, OpUpdate)
@ -2610,15 +2369,3 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error)
return nil, fmt.Errorf("ent: unknown User mutation op: %q", m.Op()) return nil, fmt.Errorf("ent: unknown User mutation op: %q", m.Op())
} }
} }
// hooks and interceptors per client, for fast access.
type (
hooks struct {
Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item,
ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Hook
}
inters struct {
Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item,
ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Interceptor
}
)

View file

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

View file

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

View file

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/document"
@ -31,7 +30,6 @@ type Document struct {
// The values are being populated by the DocumentQuery when eager-loading is set. // The values are being populated by the DocumentQuery when eager-loading is set.
Edges DocumentEdges `json:"edges"` Edges DocumentEdges `json:"edges"`
group_documents *uuid.UUID group_documents *uuid.UUID
selectValues sql.SelectValues
} }
// DocumentEdges holds the relations/edges for other nodes in the graph. // DocumentEdges holds the relations/edges for other nodes in the graph.
@ -81,7 +79,7 @@ func (*Document) scanValues(columns []string) ([]any, error) {
case document.ForeignKeys[0]: // group_documents case document.ForeignKeys[0]: // group_documents
values[i] = &sql.NullScanner{S: new(uuid.UUID)} values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default: default:
values[i] = new(sql.UnknownType) return nil, fmt.Errorf("unexpected column %q for type Document", columns[i])
} }
} }
return values, nil return values, nil
@ -132,19 +130,11 @@ func (d *Document) assignValues(columns []string, values []any) error {
d.group_documents = new(uuid.UUID) d.group_documents = new(uuid.UUID)
*d.group_documents = *value.S.(*uuid.UUID) *d.group_documents = *value.S.(*uuid.UUID)
} }
default:
d.selectValues.Set(columns[i], values[i])
} }
} }
return nil return nil
} }
// Value returns the ent.Value that was dynamically selected and assigned to the Document.
// This includes values selected through modifiers, order, etc.
func (d *Document) Value(name string) (ent.Value, error) {
return d.selectValues.Get(name)
}
// QueryGroup queries the "group" edge of the Document entity. // QueryGroup queries the "group" edge of the Document entity.
func (d *Document) QueryGroup() *GroupQuery { func (d *Document) QueryGroup() *GroupQuery {
return NewDocumentClient(d.config).QueryGroup(d) return NewDocumentClient(d.config).QueryGroup(d)

View file

@ -5,8 +5,6 @@ package document
import ( import (
"time" "time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -89,66 +87,3 @@ var (
// DefaultID holds the default value on creation for the "id" field. // DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID DefaultID func() uuid.UUID
) )
// OrderOption defines the ordering options for the Document queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByTitle orders the results by the title field.
func ByTitle(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTitle, opts...).ToFunc()
}
// ByPath orders the results by the path field.
func ByPath(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPath, opts...).ToFunc()
}
// ByGroupField orders the results by group field.
func ByGroupField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newGroupStep(), sql.OrderByField(field, opts...))
}
}
// ByAttachmentsCount orders the results by attachments count.
func ByAttachmentsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newAttachmentsStep(), opts...)
}
}
// ByAttachments orders the results by attachments terms.
func ByAttachments(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newAttachmentsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newGroupStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(GroupInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
)
}
func newAttachmentsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AttachmentsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, AttachmentsTable, AttachmentsColumn),
)
}

View file

@ -300,7 +300,11 @@ func HasGroup() predicate.Document {
// HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates). // HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates).
func HasGroupWith(preds ...predicate.Group) predicate.Document { func HasGroupWith(preds ...predicate.Group) predicate.Document {
return predicate.Document(func(s *sql.Selector) { return predicate.Document(func(s *sql.Selector) {
step := newGroupStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(GroupInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -323,7 +327,11 @@ func HasAttachments() predicate.Document {
// HasAttachmentsWith applies the HasEdge predicate on the "attachments" edge with a given conditions (other predicates). // HasAttachmentsWith applies the HasEdge predicate on the "attachments" edge with a given conditions (other predicates).
func HasAttachmentsWith(preds ...predicate.Attachment) predicate.Document { func HasAttachmentsWith(preds ...predicate.Attachment) predicate.Document {
return predicate.Document(func(s *sql.Selector) { return predicate.Document(func(s *sql.Selector) {
step := newAttachmentsStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AttachmentsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, AttachmentsTable, AttachmentsColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -334,15 +342,32 @@ func HasAttachmentsWith(preds ...predicate.Attachment) predicate.Document {
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.Document) predicate.Document { func And(predicates ...predicate.Document) predicate.Document {
return predicate.Document(sql.AndPredicates(predicates...)) return predicate.Document(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for _, p := range predicates {
p(s1)
}
s.Where(s1.P())
})
} }
// Or groups predicates with the OR operator between them. // Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Document) predicate.Document { func Or(predicates ...predicate.Document) predicate.Document {
return predicate.Document(sql.OrPredicates(predicates...)) return predicate.Document(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for i, p := range predicates {
if i > 0 {
s1.Or()
}
p(s1)
}
s.Where(s1.P())
})
} }
// Not applies the not operator on the given predicate. // Not applies the not operator on the given predicate.
func Not(p predicate.Document) predicate.Document { func Not(p predicate.Document) predicate.Document {
return predicate.Document(sql.NotPredicates(p)) return predicate.Document(func(s *sql.Selector) {
p(s.Not())
})
} }

View file

@ -111,7 +111,7 @@ func (dc *DocumentCreate) Mutation() *DocumentMutation {
// Save creates the Document in the database. // Save creates the Document in the database.
func (dc *DocumentCreate) Save(ctx context.Context) (*Document, error) { func (dc *DocumentCreate) Save(ctx context.Context) (*Document, error) {
dc.defaults() dc.defaults()
return withHooks(ctx, dc.sqlSave, dc.mutation, dc.hooks) return withHooks[*Document, DocumentMutation](ctx, dc.sqlSave, dc.mutation, dc.hooks)
} }
// SaveX calls Save and panics if Save returns an error. // SaveX calls Save and panics if Save returns an error.
@ -238,7 +238,10 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
Columns: []string{document.GroupColumn}, Columns: []string{document.GroupColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -255,7 +258,10 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
Columns: []string{document.AttachmentsColumn}, Columns: []string{document.AttachmentsColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -269,15 +275,11 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
// DocumentCreateBulk is the builder for creating many Document entities in bulk. // DocumentCreateBulk is the builder for creating many Document entities in bulk.
type DocumentCreateBulk struct { type DocumentCreateBulk struct {
config config
err error
builders []*DocumentCreate builders []*DocumentCreate
} }
// Save creates the Document entities in the database. // Save creates the Document entities in the database.
func (dcb *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) { func (dcb *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) {
if dcb.err != nil {
return nil, dcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(dcb.builders)) specs := make([]*sqlgraph.CreateSpec, len(dcb.builders))
nodes := make([]*Document, len(dcb.builders)) nodes := make([]*Document, len(dcb.builders))
mutators := make([]Mutator, len(dcb.builders)) mutators := make([]Mutator, len(dcb.builders))
@ -294,8 +296,8 @@ func (dcb *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) {
return nil, err return nil, err
} }
builder.mutation = mutation builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec() nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 { if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, dcb.builders[i+1].mutation) _, err = mutators[i+1].Mutate(root, dcb.builders[i+1].mutation)
} else { } else {

View file

@ -27,7 +27,7 @@ func (dd *DocumentDelete) Where(ps ...predicate.Document) *DocumentDelete {
// Exec executes the deletion query and returns how many vertices were deleted. // Exec executes the deletion query and returns how many vertices were deleted.
func (dd *DocumentDelete) Exec(ctx context.Context) (int, error) { func (dd *DocumentDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, dd.sqlExec, dd.mutation, dd.hooks) return withHooks[int, DocumentMutation](ctx, dd.sqlExec, dd.mutation, dd.hooks)
} }
// ExecX is like Exec, but panics if an error occurs. // ExecX is like Exec, but panics if an error occurs.

View file

@ -22,7 +22,7 @@ import (
type DocumentQuery struct { type DocumentQuery struct {
config config
ctx *QueryContext ctx *QueryContext
order []document.OrderOption order []OrderFunc
inters []Interceptor inters []Interceptor
predicates []predicate.Document predicates []predicate.Document
withGroup *GroupQuery withGroup *GroupQuery
@ -59,7 +59,7 @@ func (dq *DocumentQuery) Unique(unique bool) *DocumentQuery {
} }
// Order specifies how the records should be ordered. // Order specifies how the records should be ordered.
func (dq *DocumentQuery) Order(o ...document.OrderOption) *DocumentQuery { func (dq *DocumentQuery) Order(o ...OrderFunc) *DocumentQuery {
dq.order = append(dq.order, o...) dq.order = append(dq.order, o...)
return dq return dq
} }
@ -297,7 +297,7 @@ func (dq *DocumentQuery) Clone() *DocumentQuery {
return &DocumentQuery{ return &DocumentQuery{
config: dq.config, config: dq.config,
ctx: dq.ctx.Clone(), ctx: dq.ctx.Clone(),
order: append([]document.OrderOption{}, dq.order...), order: append([]OrderFunc{}, dq.order...),
inters: append([]Interceptor{}, dq.inters...), inters: append([]Interceptor{}, dq.inters...),
predicates: append([]predicate.Document{}, dq.predicates...), predicates: append([]predicate.Document{}, dq.predicates...),
withGroup: dq.withGroup.Clone(), withGroup: dq.withGroup.Clone(),
@ -498,7 +498,7 @@ func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQ
} }
query.withFKs = true query.withFKs = true
query.Where(predicate.Attachment(func(s *sql.Selector) { query.Where(predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(document.AttachmentsColumn), fks...)) s.Where(sql.InValues(document.AttachmentsColumn, fks...))
})) }))
neighbors, err := query.All(ctx) neighbors, err := query.All(ctx)
if err != nil { if err != nil {
@ -511,7 +511,7 @@ func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQ
} }
node, ok := nodeids[*fk] node, ok := nodeids[*fk]
if !ok { if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "document_attachments" returned %v for node %v`, *fk, n.ID) return fmt.Errorf(`unexpected foreign-key "document_attachments" returned %v for node %v`, *fk, n.ID)
} }
assign(node, n) assign(node, n)
} }

View file

@ -43,28 +43,12 @@ func (du *DocumentUpdate) SetTitle(s string) *DocumentUpdate {
return du return du
} }
// SetNillableTitle sets the "title" field if the given value is not nil.
func (du *DocumentUpdate) SetNillableTitle(s *string) *DocumentUpdate {
if s != nil {
du.SetTitle(*s)
}
return du
}
// SetPath sets the "path" field. // SetPath sets the "path" field.
func (du *DocumentUpdate) SetPath(s string) *DocumentUpdate { func (du *DocumentUpdate) SetPath(s string) *DocumentUpdate {
du.mutation.SetPath(s) du.mutation.SetPath(s)
return du return du
} }
// SetNillablePath sets the "path" field if the given value is not nil.
func (du *DocumentUpdate) SetNillablePath(s *string) *DocumentUpdate {
if s != nil {
du.SetPath(*s)
}
return du
}
// SetGroupID sets the "group" edge to the Group entity by ID. // SetGroupID sets the "group" edge to the Group entity by ID.
func (du *DocumentUpdate) SetGroupID(id uuid.UUID) *DocumentUpdate { func (du *DocumentUpdate) SetGroupID(id uuid.UUID) *DocumentUpdate {
du.mutation.SetGroupID(id) du.mutation.SetGroupID(id)
@ -126,7 +110,7 @@ func (du *DocumentUpdate) RemoveAttachments(a ...*Attachment) *DocumentUpdate {
// Save executes the query and returns the number of nodes affected by the update operation. // Save executes the query and returns the number of nodes affected by the update operation.
func (du *DocumentUpdate) Save(ctx context.Context) (int, error) { func (du *DocumentUpdate) Save(ctx context.Context) (int, error) {
du.defaults() du.defaults()
return withHooks(ctx, du.sqlSave, du.mutation, du.hooks) return withHooks[int, DocumentMutation](ctx, du.sqlSave, du.mutation, du.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -206,7 +190,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.GroupColumn}, Columns: []string{document.GroupColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -219,7 +206,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.GroupColumn}, Columns: []string{document.GroupColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -235,7 +225,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn}, Columns: []string{document.AttachmentsColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -248,7 +241,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn}, Columns: []string{document.AttachmentsColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -264,7 +260,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn}, Columns: []string{document.AttachmentsColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -304,28 +303,12 @@ func (duo *DocumentUpdateOne) SetTitle(s string) *DocumentUpdateOne {
return duo return duo
} }
// SetNillableTitle sets the "title" field if the given value is not nil.
func (duo *DocumentUpdateOne) SetNillableTitle(s *string) *DocumentUpdateOne {
if s != nil {
duo.SetTitle(*s)
}
return duo
}
// SetPath sets the "path" field. // SetPath sets the "path" field.
func (duo *DocumentUpdateOne) SetPath(s string) *DocumentUpdateOne { func (duo *DocumentUpdateOne) SetPath(s string) *DocumentUpdateOne {
duo.mutation.SetPath(s) duo.mutation.SetPath(s)
return duo return duo
} }
// SetNillablePath sets the "path" field if the given value is not nil.
func (duo *DocumentUpdateOne) SetNillablePath(s *string) *DocumentUpdateOne {
if s != nil {
duo.SetPath(*s)
}
return duo
}
// SetGroupID sets the "group" edge to the Group entity by ID. // SetGroupID sets the "group" edge to the Group entity by ID.
func (duo *DocumentUpdateOne) SetGroupID(id uuid.UUID) *DocumentUpdateOne { func (duo *DocumentUpdateOne) SetGroupID(id uuid.UUID) *DocumentUpdateOne {
duo.mutation.SetGroupID(id) duo.mutation.SetGroupID(id)
@ -400,7 +383,7 @@ func (duo *DocumentUpdateOne) Select(field string, fields ...string) *DocumentUp
// Save executes the query and returns the updated Document entity. // Save executes the query and returns the updated Document entity.
func (duo *DocumentUpdateOne) Save(ctx context.Context) (*Document, error) { func (duo *DocumentUpdateOne) Save(ctx context.Context) (*Document, error) {
duo.defaults() duo.defaults()
return withHooks(ctx, duo.sqlSave, duo.mutation, duo.hooks) return withHooks[*Document, DocumentMutation](ctx, duo.sqlSave, duo.mutation, duo.hooks)
} }
// SaveX is like Save, but panics if an error occurs. // SaveX is like Save, but panics if an error occurs.
@ -497,7 +480,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.GroupColumn}, Columns: []string{document.GroupColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -510,7 +496,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.GroupColumn}, Columns: []string{document.GroupColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -526,7 +515,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn}, Columns: []string{document.AttachmentsColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}, },
} }
_spec.Edges.Clear = append(_spec.Edges.Clear, edge) _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
@ -539,7 +531,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn}, Columns: []string{document.AttachmentsColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {
@ -555,7 +550,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn}, Columns: []string{document.AttachmentsColumn},
Bidi: false, Bidi: false,
Target: &sqlgraph.EdgeTarget{ Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID), IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}, },
} }
for _, k := range nodes { for _, k := range nodes {

View file

@ -7,7 +7,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
"sync"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
@ -46,68 +45,46 @@ type (
MutateFunc = ent.MutateFunc MutateFunc = ent.MutateFunc
) )
type clientCtxKey struct{}
// FromContext returns a Client stored inside a context, or nil if there isn't one.
func FromContext(ctx context.Context) *Client {
c, _ := ctx.Value(clientCtxKey{}).(*Client)
return c
}
// NewContext returns a new context with the given Client attached.
func NewContext(parent context.Context, c *Client) context.Context {
return context.WithValue(parent, clientCtxKey{}, c)
}
type txCtxKey struct{}
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
func TxFromContext(ctx context.Context) *Tx {
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
return tx
}
// NewTxContext returns a new context with the given Tx attached.
func NewTxContext(parent context.Context, tx *Tx) context.Context {
return context.WithValue(parent, txCtxKey{}, tx)
}
// OrderFunc applies an ordering on the sql selector. // OrderFunc applies an ordering on the sql selector.
// Deprecated: Use Asc/Desc functions or the package builders instead.
type OrderFunc func(*sql.Selector) type OrderFunc func(*sql.Selector)
var ( // columnChecker returns a function indicates if the column exists in the given column.
initCheck sync.Once func columnChecker(table string) func(string) error {
columnCheck sql.ColumnCheck checks := map[string]func(string) bool{
) attachment.Table: attachment.ValidColumn,
authroles.Table: authroles.ValidColumn,
// columnChecker checks if the column exists in the given table. authtokens.Table: authtokens.ValidColumn,
func checkColumn(table, column string) error { document.Table: document.ValidColumn,
initCheck.Do(func() { group.Table: group.ValidColumn,
columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ groupinvitationtoken.Table: groupinvitationtoken.ValidColumn,
attachment.Table: attachment.ValidColumn, item.Table: item.ValidColumn,
authroles.Table: authroles.ValidColumn, itemfield.Table: itemfield.ValidColumn,
authtokens.Table: authtokens.ValidColumn, label.Table: label.ValidColumn,
document.Table: document.ValidColumn, location.Table: location.ValidColumn,
group.Table: group.ValidColumn, maintenanceentry.Table: maintenanceentry.ValidColumn,
groupinvitationtoken.Table: groupinvitationtoken.ValidColumn, notifier.Table: notifier.ValidColumn,
item.Table: item.ValidColumn, user.Table: user.ValidColumn,
itemfield.Table: itemfield.ValidColumn, }
label.Table: label.ValidColumn, check, ok := checks[table]
location.Table: location.ValidColumn, if !ok {
maintenanceentry.Table: maintenanceentry.ValidColumn, return func(string) error {
notifier.Table: notifier.ValidColumn, return fmt.Errorf("unknown table %q", table)
user.Table: user.ValidColumn, }
}) }
}) return func(column string) error {
return columnCheck(table, column) if !check(column) {
return fmt.Errorf("unknown column %q for table %q", column, table)
}
return nil
}
} }
// Asc applies the given fields in ASC order. // Asc applies the given fields in ASC order.
func Asc(fields ...string) func(*sql.Selector) { func Asc(fields ...string) OrderFunc {
return func(s *sql.Selector) { return func(s *sql.Selector) {
check := columnChecker(s.TableName())
for _, f := range fields { for _, f := range fields {
if err := checkColumn(s.TableName(), f); err != nil { if err := check(f); err != nil {
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
} }
s.OrderBy(sql.Asc(s.C(f))) s.OrderBy(sql.Asc(s.C(f)))
@ -116,10 +93,11 @@ func Asc(fields ...string) func(*sql.Selector) {
} }
// Desc applies the given fields in DESC order. // Desc applies the given fields in DESC order.
func Desc(fields ...string) func(*sql.Selector) { func Desc(fields ...string) OrderFunc {
return func(s *sql.Selector) { return func(s *sql.Selector) {
check := columnChecker(s.TableName())
for _, f := range fields { for _, f := range fields {
if err := checkColumn(s.TableName(), f); err != nil { if err := check(f); err != nil {
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
} }
s.OrderBy(sql.Desc(s.C(f))) s.OrderBy(sql.Desc(s.C(f)))
@ -151,7 +129,8 @@ func Count() AggregateFunc {
// Max applies the "max" aggregation function on the given field of each group. // Max applies the "max" aggregation function on the given field of each group.
func Max(field string) AggregateFunc { func Max(field string) AggregateFunc {
return func(s *sql.Selector) string { return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil { check := columnChecker(s.TableName())
if err := check(field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return "" return ""
} }
@ -162,7 +141,8 @@ func Max(field string) AggregateFunc {
// Mean applies the "mean" aggregation function on the given field of each group. // Mean applies the "mean" aggregation function on the given field of each group.
func Mean(field string) AggregateFunc { func Mean(field string) AggregateFunc {
return func(s *sql.Selector) string { return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil { check := columnChecker(s.TableName())
if err := check(field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return "" return ""
} }
@ -173,7 +153,8 @@ func Mean(field string) AggregateFunc {
// Min applies the "min" aggregation function on the given field of each group. // Min applies the "min" aggregation function on the given field of each group.
func Min(field string) AggregateFunc { func Min(field string) AggregateFunc {
return func(s *sql.Selector) string { return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil { check := columnChecker(s.TableName())
if err := check(field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return "" return ""
} }
@ -184,7 +165,8 @@ func Min(field string) AggregateFunc {
// Sum applies the "sum" aggregation function on the given field of each group. // Sum applies the "sum" aggregation function on the given field of each group.
func Sum(field string) AggregateFunc { func Sum(field string) AggregateFunc {
return func(s *sql.Selector) string { return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil { check := columnChecker(s.TableName())
if err := check(field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return "" return ""
} }
@ -521,7 +503,7 @@ func withHooks[V Value, M any, PM interface {
return exec(ctx) return exec(ctx)
} }
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutationT, ok := any(m).(PM) mutationT, ok := m.(PM)
if !ok { if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m) return nil, fmt.Errorf("unexpected mutation type %T", m)
} }

View file

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/group"
@ -25,11 +24,10 @@ type Group struct {
// Name holds the value of the "name" field. // Name holds the value of the "name" field.
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// Currency holds the value of the "currency" field. // Currency holds the value of the "currency" field.
Currency string `json:"currency,omitempty"` Currency group.Currency `json:"currency,omitempty"`
// Edges holds the relations/edges for other nodes in the graph. // Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the GroupQuery when eager-loading is set. // The values are being populated by the GroupQuery when eager-loading is set.
Edges GroupEdges `json:"edges"` Edges GroupEdges `json:"edges"`
selectValues sql.SelectValues
} }
// GroupEdges holds the relations/edges for other nodes in the graph. // GroupEdges holds the relations/edges for other nodes in the graph.
@ -128,7 +126,7 @@ func (*Group) scanValues(columns []string) ([]any, error) {
case group.FieldID: case group.FieldID:
values[i] = new(uuid.UUID) values[i] = new(uuid.UUID)
default: default:
values[i] = new(sql.UnknownType) return nil, fmt.Errorf("unexpected column %q for type Group", columns[i])
} }
} }
return values, nil return values, nil
@ -170,21 +168,13 @@ func (gr *Group) assignValues(columns []string, values []any) error {
if value, ok := values[i].(*sql.NullString); !ok { if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field currency", values[i]) return fmt.Errorf("unexpected type %T for field currency", values[i])
} else if value.Valid { } else if value.Valid {
gr.Currency = value.String gr.Currency = group.Currency(value.String)
} }
default:
gr.selectValues.Set(columns[i], values[i])
} }
} }
return nil return nil
} }
// Value returns the ent.Value that was dynamically selected and assigned to the Group.
// This includes values selected through modifiers, order, etc.
func (gr *Group) Value(name string) (ent.Value, error) {
return gr.selectValues.Get(name)
}
// QueryUsers queries the "users" edge of the Group entity. // QueryUsers queries the "users" edge of the Group entity.
func (gr *Group) QueryUsers() *UserQuery { func (gr *Group) QueryUsers() *UserQuery {
return NewGroupClient(gr.config).QueryUsers(gr) return NewGroupClient(gr.config).QueryUsers(gr)
@ -253,7 +243,7 @@ func (gr *Group) String() string {
builder.WriteString(gr.Name) builder.WriteString(gr.Name)
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("currency=") builder.WriteString("currency=")
builder.WriteString(gr.Currency) builder.WriteString(fmt.Sprintf("%v", gr.Currency))
builder.WriteByte(')') builder.WriteByte(')')
return builder.String() return builder.String()
} }

View file

@ -3,10 +3,9 @@
package group package group
import ( import (
"fmt"
"time" "time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -118,183 +117,46 @@ var (
UpdateDefaultUpdatedAt func() time.Time UpdateDefaultUpdatedAt func() time.Time
// NameValidator is a validator for the "name" field. It is called by the builders before save. // NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error NameValidator func(string) error
// DefaultCurrency holds the default value on creation for the "currency" field.
DefaultCurrency string
// DefaultID holds the default value on creation for the "id" field. // DefaultID holds the default value on creation for the "id" field.
DefaultID func() uuid.UUID DefaultID func() uuid.UUID
) )
// OrderOption defines the ordering options for the Group queries. // Currency defines the type for the "currency" enum field.
type OrderOption func(*sql.Selector) type Currency string
// ByID orders the results by the id field. // CurrencyUsd is the default value of the Currency enum.
func ByID(opts ...sql.OrderTermOption) OrderOption { const DefaultCurrency = CurrencyUsd
return sql.OrderByField(FieldID, opts...).ToFunc()
// Currency values.
const (
CurrencyUsd Currency = "usd"
CurrencyEur Currency = "eur"
CurrencyGbp Currency = "gbp"
CurrencyJpy Currency = "jpy"
CurrencyZar Currency = "zar"
CurrencyAud Currency = "aud"
CurrencyNok Currency = "nok"
CurrencySek Currency = "sek"
CurrencyDkk Currency = "dkk"
CurrencyInr Currency = "inr"
CurrencyRmb Currency = "rmb"
CurrencyBgn Currency = "bgn"
CurrencyChf Currency = "chf"
CurrencyPln Currency = "pln"
CurrencyTry Currency = "try"
CurrencyRon Currency = "ron"
)
func (c Currency) String() string {
return string(c)
} }
// ByCreatedAt orders the results by the created_at field. // CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { func CurrencyValidator(c Currency) error {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() switch c {
} case CurrencyUsd, CurrencyEur, CurrencyGbp, CurrencyJpy, CurrencyZar, CurrencyAud, CurrencyNok, CurrencySek, CurrencyDkk, CurrencyInr, CurrencyRmb, CurrencyBgn, CurrencyChf, CurrencyPln, CurrencyTry, CurrencyRon:
return nil
// ByUpdatedAt orders the results by the updated_at field. default:
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { return fmt.Errorf("group: invalid enum value for currency field: %q", c)
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByCurrency orders the results by the currency field.
func ByCurrency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCurrency, opts...).ToFunc()
}
// ByUsersCount orders the results by users count.
func ByUsersCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newUsersStep(), opts...)
} }
} }
// ByUsers orders the results by users terms.
func ByUsers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUsersStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByLocationsCount orders the results by locations count.
func ByLocationsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newLocationsStep(), opts...)
}
}
// ByLocations orders the results by locations terms.
func ByLocations(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newLocationsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByItemsCount orders the results by items count.
func ByItemsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newItemsStep(), opts...)
}
}
// ByItems orders the results by items terms.
func ByItems(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newItemsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByLabelsCount orders the results by labels count.
func ByLabelsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newLabelsStep(), opts...)
}
}
// ByLabels orders the results by labels terms.
func ByLabels(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newLabelsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByDocumentsCount orders the results by documents count.
func ByDocumentsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newDocumentsStep(), opts...)
}
}
// ByDocuments orders the results by documents terms.
func ByDocuments(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newDocumentsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByInvitationTokensCount orders the results by invitation_tokens count.
func ByInvitationTokensCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newInvitationTokensStep(), opts...)
}
}
// ByInvitationTokens orders the results by invitation_tokens terms.
func ByInvitationTokens(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newInvitationTokensStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByNotifiersCount orders the results by notifiers count.
func ByNotifiersCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newNotifiersStep(), opts...)
}
}
// ByNotifiers orders the results by notifiers terms.
func ByNotifiers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newNotifiersStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newUsersStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UsersInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, UsersTable, UsersColumn),
)
}
func newLocationsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(LocationsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, LocationsTable, LocationsColumn),
)
}
func newItemsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ItemsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ItemsTable, ItemsColumn),
)
}
func newLabelsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(LabelsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, LabelsTable, LabelsColumn),
)
}
func newDocumentsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DocumentsTable, DocumentsColumn),
)
}
func newInvitationTokensStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(InvitationTokensInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, InvitationTokensTable, InvitationTokensColumn),
)
}
func newNotifiersStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(NotifiersInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn),
)
}

View file

@ -71,11 +71,6 @@ func Name(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldName, v)) return predicate.Group(sql.FieldEQ(FieldName, v))
} }
// Currency applies equality check predicate on the "currency" field. It's identical to CurrencyEQ.
func Currency(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldCurrency, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field. // CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Group { func CreatedAtEQ(v time.Time) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldCreatedAt, v)) return predicate.Group(sql.FieldEQ(FieldCreatedAt, v))
@ -222,70 +217,25 @@ func NameContainsFold(v string) predicate.Group {
} }
// CurrencyEQ applies the EQ predicate on the "currency" field. // CurrencyEQ applies the EQ predicate on the "currency" field.
func CurrencyEQ(v string) predicate.Group { func CurrencyEQ(v Currency) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldCurrency, v)) return predicate.Group(sql.FieldEQ(FieldCurrency, v))
} }
// CurrencyNEQ applies the NEQ predicate on the "currency" field. // CurrencyNEQ applies the NEQ predicate on the "currency" field.
func CurrencyNEQ(v string) predicate.Group { func CurrencyNEQ(v Currency) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldCurrency, v)) return predicate.Group(sql.FieldNEQ(FieldCurrency, v))
} }
// CurrencyIn applies the In predicate on the "currency" field. // CurrencyIn applies the In predicate on the "currency" field.
func CurrencyIn(vs ...string) predicate.Group { func CurrencyIn(vs ...Currency) predicate.Group {
return predicate.Group(sql.FieldIn(FieldCurrency, vs...)) return predicate.Group(sql.FieldIn(FieldCurrency, vs...))
} }
// CurrencyNotIn applies the NotIn predicate on the "currency" field. // CurrencyNotIn applies the NotIn predicate on the "currency" field.
func CurrencyNotIn(vs ...string) predicate.Group { func CurrencyNotIn(vs ...Currency) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldCurrency, vs...)) return predicate.Group(sql.FieldNotIn(FieldCurrency, vs...))
} }
// CurrencyGT applies the GT predicate on the "currency" field.
func CurrencyGT(v string) predicate.Group {
return predicate.Group(sql.FieldGT(FieldCurrency, v))
}
// CurrencyGTE applies the GTE predicate on the "currency" field.
func CurrencyGTE(v string) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldCurrency, v))
}
// CurrencyLT applies the LT predicate on the "currency" field.
func CurrencyLT(v string) predicate.Group {
return predicate.Group(sql.FieldLT(FieldCurrency, v))
}
// CurrencyLTE applies the LTE predicate on the "currency" field.
func CurrencyLTE(v string) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldCurrency, v))
}
// CurrencyContains applies the Contains predicate on the "currency" field.
func CurrencyContains(v string) predicate.Group {
return predicate.Group(sql.FieldContains(FieldCurrency, v))
}
// CurrencyHasPrefix applies the HasPrefix predicate on the "currency" field.
func CurrencyHasPrefix(v string) predicate.Group {
return predicate.Group(sql.FieldHasPrefix(FieldCurrency, v))
}
// CurrencyHasSuffix applies the HasSuffix predicate on the "currency" field.
func CurrencyHasSuffix(v string) predicate.Group {
return predicate.Group(sql.FieldHasSuffix(FieldCurrency, v))
}
// CurrencyEqualFold applies the EqualFold predicate on the "currency" field.
func CurrencyEqualFold(v string) predicate.Group {
return predicate.Group(sql.FieldEqualFold(FieldCurrency, v))
}
// CurrencyContainsFold applies the ContainsFold predicate on the "currency" field.
func CurrencyContainsFold(v string) predicate.Group {
return predicate.Group(sql.FieldContainsFold(FieldCurrency, v))
}
// HasUsers applies the HasEdge predicate on the "users" edge. // HasUsers applies the HasEdge predicate on the "users" edge.
func HasUsers() predicate.Group { func HasUsers() predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
@ -300,7 +250,11 @@ func HasUsers() predicate.Group {
// HasUsersWith applies the HasEdge predicate on the "users" edge with a given conditions (other predicates). // HasUsersWith applies the HasEdge predicate on the "users" edge with a given conditions (other predicates).
func HasUsersWith(preds ...predicate.User) predicate.Group { func HasUsersWith(preds ...predicate.User) predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
step := newUsersStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UsersInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, UsersTable, UsersColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -323,7 +277,11 @@ func HasLocations() predicate.Group {
// HasLocationsWith applies the HasEdge predicate on the "locations" edge with a given conditions (other predicates). // HasLocationsWith applies the HasEdge predicate on the "locations" edge with a given conditions (other predicates).
func HasLocationsWith(preds ...predicate.Location) predicate.Group { func HasLocationsWith(preds ...predicate.Location) predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
step := newLocationsStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(LocationsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, LocationsTable, LocationsColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -346,7 +304,11 @@ func HasItems() predicate.Group {
// HasItemsWith applies the HasEdge predicate on the "items" edge with a given conditions (other predicates). // HasItemsWith applies the HasEdge predicate on the "items" edge with a given conditions (other predicates).
func HasItemsWith(preds ...predicate.Item) predicate.Group { func HasItemsWith(preds ...predicate.Item) predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
step := newItemsStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ItemsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ItemsTable, ItemsColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -369,7 +331,11 @@ func HasLabels() predicate.Group {
// HasLabelsWith applies the HasEdge predicate on the "labels" edge with a given conditions (other predicates). // HasLabelsWith applies the HasEdge predicate on the "labels" edge with a given conditions (other predicates).
func HasLabelsWith(preds ...predicate.Label) predicate.Group { func HasLabelsWith(preds ...predicate.Label) predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
step := newLabelsStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(LabelsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, LabelsTable, LabelsColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -392,7 +358,11 @@ func HasDocuments() predicate.Group {
// HasDocumentsWith applies the HasEdge predicate on the "documents" edge with a given conditions (other predicates). // HasDocumentsWith applies the HasEdge predicate on the "documents" edge with a given conditions (other predicates).
func HasDocumentsWith(preds ...predicate.Document) predicate.Group { func HasDocumentsWith(preds ...predicate.Document) predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
step := newDocumentsStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DocumentsTable, DocumentsColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -415,7 +385,11 @@ func HasInvitationTokens() predicate.Group {
// HasInvitationTokensWith applies the HasEdge predicate on the "invitation_tokens" edge with a given conditions (other predicates). // HasInvitationTokensWith applies the HasEdge predicate on the "invitation_tokens" edge with a given conditions (other predicates).
func HasInvitationTokensWith(preds ...predicate.GroupInvitationToken) predicate.Group { func HasInvitationTokensWith(preds ...predicate.GroupInvitationToken) predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
step := newInvitationTokensStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(InvitationTokensInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, InvitationTokensTable, InvitationTokensColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -438,7 +412,11 @@ func HasNotifiers() predicate.Group {
// HasNotifiersWith applies the HasEdge predicate on the "notifiers" edge with a given conditions (other predicates). // HasNotifiersWith applies the HasEdge predicate on the "notifiers" edge with a given conditions (other predicates).
func HasNotifiersWith(preds ...predicate.Notifier) predicate.Group { func HasNotifiersWith(preds ...predicate.Notifier) predicate.Group {
return predicate.Group(func(s *sql.Selector) { return predicate.Group(func(s *sql.Selector) {
step := newNotifiersStep() step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(NotifiersInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds { for _, p := range preds {
p(s) p(s)
@ -449,15 +427,32 @@ func HasNotifiersWith(preds ...predicate.Notifier) predicate.Group {
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.Group) predicate.Group { func And(predicates ...predicate.Group) predicate.Group {
return predicate.Group(sql.AndPredicates(predicates...)) return predicate.Group(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for _, p := range predicates {
p(s1)
}
s.Where(s1.P())
})
} }
// Or groups predicates with the OR operator between them. // Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Group) predicate.Group { func Or(predicates ...predicate.Group) predicate.Group {
return predicate.Group(sql.OrPredicates(predicates...)) return predicate.Group(func(s *sql.Selector) {
s1 := s.Clone().SetP(nil)
for i, p := range predicates {
if i > 0 {
s1.Or()
}
p(s1)
}
s.Where(s1.P())
})
} }
// Not applies the not operator on the given predicate. // Not applies the not operator on the given predicate.
func Not(p predicate.Group) predicate.Group { func Not(p predicate.Group) predicate.Group {
return predicate.Group(sql.NotPredicates(p)) return predicate.Group(func(s *sql.Selector) {
p(s.Not())
})
} }

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