Compare commits

..

11 commits

Author SHA1 Message Date
Hayden
e96da19d2b fix upload size 2022-10-06 15:24:14 -05:00
Hayden
68a27b7f82 rename repository factory 2022-10-06 15:02:15 -05:00
Hayden
1827c61314 remove console.logs 2022-10-03 21:45:12 -08:00
Hayden
76c6d25850 implement UI for url and join 2022-10-03 21:36:51 -08:00
Hayden
505099ee26 api refactor / registration tests 2022-10-03 20:09:53 -08:00
Hayden
a67316a889 fix types 2022-10-03 20:09:26 -08:00
Hayden
1143246816 refactor server to support variable options 2022-10-03 15:43:36 -08:00
Hayden
245591cb23 (wip) init token APIs 2022-10-01 14:02:14 -08:00
Hayden
c4916ea762 basic layout 2022-10-01 12:59:18 -08:00
Hayden
d6c1b789cf lowercase buttons by default 2022-10-01 12:57:51 -08:00
Hayden
72324b8439 add user profiles and theme selectors 2022-10-01 12:13:53 -08:00
502 changed files with 31301 additions and 68900 deletions

View file

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

View file

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

View file

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

1
.github/FUNDING.yml vendored
View file

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

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View file

@ -1,61 +0,0 @@
---
name: "Bug Report"
description: "Submit a bug report for the current release"
labels: ["bug"]
body:
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: This is not a feature request
required: true
- label: I added a very descriptive title to this issue.
required: true
- label: I used the GitHub search to find a similar issue and didn't find it.
required: true
- label: I searched the documentation, with the integrated search.
required: true
- label: I already read the docs and didn't find an answer.
required: true
- type: input
id: homebox-version
attributes:
label: Homebox Version
validations:
required: true
- type: textarea
id: description
attributes:
label: What is the issue you are experiencing?
placeholder: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: reproduction
attributes:
description: If you do not provide a way to reproduce the issue, your issue will likely be closed.
label: How can the maintainer reproduce the issue?
placeholder: A clear step-by-step guide on how to reproduce the issue.
validations:
required: true
- type: dropdown
id: os
attributes:
label: Deployment
description: What Deployment system are you using?
multiple: true
options:
- Docker (Linux)
- Docker (Windows)
- Docker (Synology)
- Unraid
- Other
validations:
required: true
- type: textarea
id: os-details
attributes:
label: Deployment Details
description: You can add more details about your operating system here, in particular if you chose "Other". If you are experiencing issues with deployment, please provide your docker-compose or docker commands

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,37 +0,0 @@
---
name: "Feature Request"
description: "Submit a feature request for the current release"
labels: ["feature-request"]
body:
- type: textarea
id: problem-statement
attributes:
label: What is the problem you are trying to solve with this feature?
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
- type: textarea
id: feature-solution
attributes:
label: What is the solution you are proposing?
placeholder: A clear and concise description of what you want to happen.
- type: textarea
id: feature-alternatives
attributes:
label: What alternatives have you considered?
placeholder: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
id: feature-details
attributes:
label: Additional context
placeholder: Add any other context or screenshots about the feature request here.
- type: checkboxes
id: checks
attributes:
label: Contributions
description: Please confirm the following
options:
- label: I have searched through existing issues and feature requests to see if my idea has already been proposed.
required: true
- label: If this feature is accepted, I would be willing to help implement and maintain this feature.
required: false
- label: If this feature is accepted, I'm willing to sponsor the development of this feature.
required: false

31
.github/dependabot.yml vendored Normal file
View file

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

View file

@ -7,20 +7,18 @@ jobs:
Go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.19
- name: Install Task
uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
uses: golangci/golangci-lint-action@v3
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
@ -30,7 +28,7 @@ jobs:
args: --timeout=6m
- name: Build API
run: task go:build
run: task api:build
- name: Test
run: task go:coverage
run: task api:coverage

View file

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

View file

@ -1,89 +0,0 @@
name: Frontend / E2E
on:
workflow_call:
inputs:
tag:
required: true
type: string
release:
required: false
type: boolean
default: false
secrets:
GH_TOKEN:
required: true
jobs:
publish:
name: "Publish Homebox"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.20"
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt:latest
platforms: all
- name: install buildx
id: buildx
uses: docker/setup-buildx-action@v3
with:
install: true
- name: login to container registry
run: docker login ghcr.io --username hay-kot --password $CR_PAT
env:
CR_PAT: ${{ secrets.GH_TOKEN }}
- name: build nightly image
if: ${{ inputs.release == false }}
run: |
docker build --push --no-cache \
--tag=ghcr.io/hay-kot/homebox:${{ inputs.tag }} \
--build-arg=COMMIT=$(git rev-parse HEAD) \
--build-arg=BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
- name: build nightly-rootless image
if: ${{ inputs.release == false }}
run: |
docker build --push --no-cache \
--tag=ghcr.io/hay-kot/homebox:${{ inputs.tag }}-rootless \
--build-arg=COMMIT=$(git rev-parse HEAD) \
--build-arg=BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--file Dockerfile.rootless \
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
- name: build release tagged the image
if: ${{ inputs.release == true }}
run: |
docker build --push --no-cache \
--tag ghcr.io/hay-kot/homebox:nightly \
--tag ghcr.io/hay-kot/homebox:latest \
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }} \
--build-arg VERSION=${{ inputs.tag }} \
--build-arg COMMIT=$(git rev-parse HEAD) \
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--platform linux/amd64,linux/arm64,linux/arm/v7 .
- name: build release tagged the rootless image
if: ${{ inputs.release == true }}
run: |
docker build --push --no-cache \
--tag ghcr.io/hay-kot/homebox:nightly-rootless \
--tag ghcr.io/hay-kot/homebox:latest-rootless \
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }}-rootless \
--build-arg VERSION=${{ inputs.tag }} \
--build-arg COMMIT=$(git rev-parse HEAD) \
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
--file Dockerfile.rootless .

View file

@ -1,4 +1,4 @@
name: Publish Dockers
name: Build Nightly
on:
push:
@ -9,21 +9,76 @@ env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
backend-tests:
name: "Backend Server Tests"
uses: hay-kot/homebox/.github/workflows/partial-backend.yaml@main
frontend-tests:
name: "Frontend and End-to-End Tests"
uses: hay-kot/homebox/.github/workflows/partial-frontend.yaml@main
deploy:
name: "Deploy Nightly to Fly.io"
runs-on: ubuntu-latest
needs:
- backend-tests
- frontend-tests
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
publish-nightly:
publish:
name: "Publish Nightly"
if: github.event_name != 'release'
uses: hay-kot/homebox/.github/workflows/partial-publish.yaml@main
needs:
- backend-tests
- frontend-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
tag: nightly
secrets:
GH_TOKEN: ${{ secrets.CR_PAT }}
go-version: 1.19
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v1
with:
image: tonistiigi/binfmt:latest
platforms: all
- name: install buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
install: true
- name: login to container registry
run: docker login ghcr.io --username hay-kot --password $CR_PAT
env:
CR_PAT: ${{ secrets.CR_PAT }}
- name: build the image
run: |
docker build --push \
--tag ghcr.io/hay-kot/homebox:nightly \
--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 .
deploy-docs:
name: Deploy docs
needs:
- publish
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG_FILE: docs/mkdocs.yml
EXTRA_PACKAGES: build-base

View file

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

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

12
.gitignore vendored
View file

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

View file

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

View file

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

47
.vscode/launch.json vendored
View file

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

28
.vscode/settings.json vendored
View file

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

View file

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

View file

@ -1,45 +1,45 @@
# Build Nuxt
FROM r.batts.cloud/nodejs:18 as frontend-builder
FROM node:17-alpine as frontend-builder
WORKDIR /app
RUN npm install -g pnpm@latest-9
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 r.batts.cloud/golang:1.24 AS builder
FROM golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
RUN apt update && \
apt install -y git build-essential gcc g++
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" \
COPY --from=frontend-builder /app/.output/public ./app/api/public
RUN CGO_ENABLED=1 GOOS=linux go build \
-ldflags "-s -w -X main.Commit=$COMMIT -X main.BuildTime=$BUILD_TIME" \
-o /go/bin/api \
-v ./app/api/*.go
# Production Stage
FROM r.batts.cloud/debian:trixie
FROM alpine:latest
ENV HBOX_MODE=production
ENV HBOX_STORAGE_DATA=/data/
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_pragma=busy_timeout=2000&_pragma=journal_mode=WAL&_fk=1
ENV HBOX_STORAGE_SQLITE_URL=/data/homebox.db?_fk=1
RUN apk --no-cache add ca-certificates
RUN mkdir /app
COPY --from=builder /go/bin/api /app
RUN chmod +x /app/api
LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
EXPOSE 7745
WORKDIR /app
VOLUME [ "/data" ]

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

View file

@ -1,145 +1,63 @@
version: "3"
env:
HBOX_LOG_LEVEL: debug
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_pragma=busy_timeout=1000&_pragma=journal_mode=WAL&_fk=1
HBOX_OPTIONS_ALLOW_REGISTRATION: true
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
tasks:
setup:
desc: Install development dependencies
cmds:
- go install github.com/swaggo/swag/cmd/swag@latest
- cd backend && go mod tidy
- cd frontend && pnpm install --shamefully-hoist
swag:
desc: Generate swagger docs
dir: backend/app/api/static/
vars:
API: "../"
INTERNAL: "../../../internal"
PKGS: "../../../pkgs"
cmds:
- swag fmt --dir={{ .API }}
- swag init --dir={{ .API }},{{ .INTERNAL }}/core/services,{{ .INTERNAL }}/data/repo --parseDependency
sources:
- "./backend/app/api/**/*"
- "./backend/internal/data/**"
- "./backend/internal/core/services/**/*"
- "./backend/app/tools/typegen/main.go"
typescript-types:
desc: Generates typescript types from swagger definition
generate:
desc: |
Generates collateral files from the backend project
including swagger docs and typescripts type for the frontend
deps:
- db:generate
cmds:
- cd backend/app/api/ && swag fmt
- cd backend/app/api/ && swag init --dir=./,../../internal,../../pkgs
- |
npx swagger-typescript-api \
--no-client \
--modular \
--path ./backend/app/api/static/docs/swagger.json \
--path ./backend/app/api/docs/swagger.json \
--output ./frontend/lib/api/types
- go run ./backend/app/tools/typegen/main.go ./frontend/lib/api/types/data-contracts.ts
python3 ./scripts/process-types.py ./frontend/lib/api/types/data-contracts.ts
sources:
- ./backend/app/tools/typegen/main.go
- ./backend/app/api/static/docs/swagger.json
- "./backend/app/api/**/*"
- "./backend/app/internal/types/**/*"
- "./scripts/process-types.py"
generates:
- "./frontend/lib/api/types/data-contracts.ts"
- "./backend/app/api/docs/swagger.json"
- "./backend/app/api/docs/swagger.yaml"
generate:
deps:
- db:generate
cmds:
- task: swag
- task: typescript-types
- cp ./backend/app/api/static/docs/swagger.json docs/docs/api/openapi-2.0.json
go:run:
api:
desc: Starts the backend api server (depends on generate task)
dir: backend
deps:
- generate
cmds:
- go run ./app/api/ {{ .CLI_ARGS }}
- cd backend && go run ./app/api/ {{ .CLI_ARGS }}
silent: false
go:test:
desc: Runs all go tests using gotestsum - supports passing gotestsum args
dir: backend
api:build:
cmds:
- gotestsum {{ .CLI_ARGS }} ./...
go:coverage:
desc: Runs all go tests with -race flag and generates a coverage report
dir: backend
cmds:
- go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
- cd backend && go build ./app/api/
silent: true
go:tidy:
desc: Runs go mod tidy on the backend
dir: backend
api:test:
cmds:
- go mod tidy
- cd backend && go test ./app/api/
silent: true
go:lint:
desc: Runs golangci-lint
dir: backend
api:watch:
cmds:
- golangci-lint run ./...
- cd backend && gotestsum --watch ./...
go:all:
desc: Runs all go test and lint related tasks
api:coverage:
cmds:
- task: go:tidy
- task: go:lint
- task: go:test
go:build:
desc: Builds the backend binary
dir: backend
cmds:
- go build -o ../build/backend ./app/api
db:generate:
desc: Run Entgo.io Code Generation
dir: backend/internal/
cmds:
- |
go generate ./...
sources:
- "./backend/internal/data/ent/schema/**/*"
db:migration:
desc: Runs the database diff engine to generate a SQL migration files
deps:
- db:generate
cmds:
- cd backend && go run app/tools/migrations/main.go {{ .CLI_ARGS }}
ui:watch:
desc: Starts the vitest test runner in watch mode
dir: frontend
cmds:
- pnpm run test:watch
ui:dev:
desc: Run frontend development server
dir: frontend
cmds:
- pnpm dev
ui:fix:
desc: Runs prettier and eslint on the frontend
dir: frontend
cmds:
- pnpm run lint:fix
ui:check:
desc: Runs type checking
dir: frontend
cmds:
- pnpm run typecheck
- cd backend && go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
silent: true
test:ci:
desc: Runs end-to-end test on a live server (only for use in CI)
cmds:
- cd backend && go build ./app/api
- backend/api &
@ -147,11 +65,30 @@ tasks:
- cd frontend && pnpm run test:ci
silent: true
pr:
desc: Runs all tasks required for a PR
frontend:watch:
desc: Starts the vitest test runner in watch mode
cmds:
- task: generate
- task: go:all
- task: ui:check
- task: ui:fix
- task: test:ci
- cd frontend && pnpm vitest --watch
frontend:
desc: Run frontend development server
cmds:
- cd frontend && pnpm dev
db:generate:
desc: Run Entgo.io Code Generation
cmds:
- |
cd backend && go generate ./... \
--template=ent/schema/templates/has_id.tmpl
sources:
- "./backend/ent/schema/**/*"
generates:
- "./backend/ent/"
db:migration:
desc: Runs the database diff engine to generate a SQL migration files
deps:
- db:generate
cmds:
- cd backend && go run app/migrations/main.go {{ .CLI_ARGS }}

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

View file

@ -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

@ -1,66 +0,0 @@
package main
import (
"context"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/rs/zerolog/log"
)
func (a *app) SetupDemo() {
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,,
,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,,
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
`
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
registration := services.UserRegistration{
Email: "demo@example.com",
Name: "Demo",
Password: "demo",
}
// First check if we've already setup a demo user and skip if so
log.Debug().Msg("Checking if demo user already exists")
_, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err == nil {
log.Info().Msg("Demo user already exists, skipping setup")
return
}
log.Debug().Msg("Demo user does not exist, setting up demo")
_, err = a.services.User.RegisterUser(ctx, registration)
if err != nil {
log.Err(err).Msg("Failed to register demo user")
log.Fatal().Msg("Failed to setup demo")
}
token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err != nil {
log.Err(err).Msg("Failed to login demo user")
log.Fatal().Msg("Failed to setup demo")
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))
if err != nil {
log.Err(err).Msg("Failed to import CSV")
log.Fatal().Msg("Failed to setup demo")
}
log.Info().Msg("Demo setup complete")
}

1654
backend/app/api/docs/docs.go Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,251 +0,0 @@
package v1
import (
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
)
const (
cookieNameToken = "hb.auth.token"
cookieNameRemember = "hb.auth.remember"
cookieNameSession = "hb.auth.session"
)
type (
TokenResponse struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
AttachmentToken string `json:"attachmentToken"`
}
LoginForm struct {
Username string `json:"username"`
Password string `json:"password"`
StayLoggedIn bool `json:"stayLoggedIn"`
}
)
type CookieContents struct {
Token string
ExpiresAt time.Time
Remember bool
}
func GetCookies(r *http.Request) (*CookieContents, error) {
cookie, err := r.Cookie(cookieNameToken)
if err != nil {
return nil, errors.New("authorization cookie is required")
}
rememberCookie, err := r.Cookie(cookieNameRemember)
if err != nil {
return nil, errors.New("remember cookie is required")
}
return &CookieContents{
Token: cookie.Value,
ExpiresAt: cookie.Expires,
Remember: rememberCookie.Value == "true",
}, nil
}
// AuthProvider is an interface that can be implemented by any authentication provider.
// to extend authentication methods for the API.
type AuthProvider interface {
// Name returns the name of the authentication provider. This should be a unique name.
// that is URL friendly.
//
// Example: "local", "ldap"
Name() string
// Authenticate is called when a user attempts to login to the API. The implementation
// should return an error if the user cannot be authenticated. If an error is returned
// the API controller will return a vague error message to the user.
//
// Authenticate should do the following:
//
// 1. Ensure that the user exists within the database (either create, or get)
// 2. On successful authentication, they must set the user cookies.
Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error)
}
// HandleAuthLogin godoc
//
// @Summary User Login
// @Tags Authentication
// @Accept x-www-form-urlencoded
// @Accept application/json
// @Param username formData string false "string" example(admin@admin.com)
// @Param password formData string false "string" example(admin)
// @Param payload body LoginForm true "Login Data"
// @Param provider query string false "auth provider"
// @Produce json
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFunc {
if len(ps) == 0 {
panic("no auth providers provided")
}
providers := make(map[string]AuthProvider)
for _, p := range ps {
log.Info().Str("name", p.Name()).Msg("registering auth provider")
providers[p.Name()] = p
}
return func(w http.ResponseWriter, r *http.Request) error {
// Extract provider query
provider := r.URL.Query().Get("provider")
if provider == "" {
provider = "local"
}
// Get the provider
p, ok := providers[provider]
if !ok {
return validate.NewRequestError(errors.New("invalid auth provider"), http.StatusBadRequest)
}
newToken, err := p.Authenticate(w, r)
if err != nil {
log.Err(err).Msg("failed to authenticate")
return server.JSON(w, http.StatusInternalServerError, err.Error())
}
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true)
return server.JSON(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
AttachmentToken: newToken.AttachmentToken,
})
}
}
// HandleAuthLogout godoc
//
// @Summary User Logout
// @Tags Authentication
// @Success 204
// @Router /v1/users/logout [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthLogout() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
token := services.UseTokenCtx(r.Context())
if token == "" {
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
}
err := ctrl.svc.User.Logout(r.Context(), token)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
ctrl.unsetCookies(w, noPort(r.Host))
return server.JSON(w, http.StatusNoContent, nil)
}
}
// HandleAuthRefresh godoc
//
// @Summary User Token Refresh
// @Description handleAuthRefresh returns a handler that will issue a new token from an existing token.
// @Description This does not validate that the user still exists within the database.
// @Tags Authentication
// @Success 200
// @Router /v1/users/refresh [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleAuthRefresh() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
requestToken := services.UseTokenCtx(r.Context())
if requestToken == "" {
return validate.NewRequestError(errors.New("no token within request context"), http.StatusUnauthorized)
}
newToken, err := ctrl.svc.User.RenewToken(r.Context(), requestToken)
if err != nil {
return validate.NewUnauthorizedError()
}
ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, false)
return server.JSON(w, http.StatusOK, newToken)
}
}
func noPort(host string) string {
return strings.Split(host, ":")[0]
}
func (ctrl *V1Controller) setCookies(w http.ResponseWriter, domain, token string, expires time.Time, remember bool) {
http.SetCookie(w, &http.Cookie{
Name: cookieNameRemember,
Value: strconv.FormatBool(remember),
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set HTTP only cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameToken,
Value: token,
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set Fake Session cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameSession,
Value: "true",
Expires: expires,
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: false,
Path: "/",
})
}
func (ctrl *V1Controller) unsetCookies(w http.ResponseWriter, domain string) {
http.SetCookie(w, &http.Cookie{
Name: cookieNameToken,
Value: "",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
http.SetCookie(w, &http.Cookie{
Name: cookieNameRemember,
Value: "false",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: true,
Path: "/",
})
// Set Fake Session cookie
http.SetCookie(w, &http.Cookie{
Name: cookieNameSession,
Value: "false",
Expires: time.Unix(0, 0),
Domain: domain,
Secure: ctrl.cookieSecure,
HttpOnly: false,
Path: "/",
})
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,68 +1,53 @@
package main
import (
"bytes"
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql/schema"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/hay-kot/homebox/backend/internal/core/currencies"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/migrations"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"github.com/hay-kot/homebox/backend/internal/web/mid"
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/graceful"
"github.com/rs/zerolog"
"github.com/hay-kot/homebox/backend/app/api/docs"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/config"
"github.com/hay-kot/homebox/backend/internal/migrations"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
_ "github.com/hay-kot/homebox/backend/pkgs/cgofreesqlite"
)
var (
version = "nightly"
commit = "HEAD"
buildTime = "now"
Version = "0.1.0"
Commit = "HEAD"
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 Go API Templates
// @version 1.0
// @description Track, Manage, and Organize your Things.
// @description This is a simple Rest API Server Template that implements some basic User and Authentication patterns to help you get started and bootstrap your next project!.
// @contact.name Don't
// @license.name MIT
// @BasePath /api
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
func main() {
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
path := ""
if len(os.Args) > 1 {
path = os.Args[1]
}
cfg, err := config.New(build(), "Homebox inventory management system")
cfg, err := config.NewConfig(path)
if err != nil {
panic(err)
}
docs.SwaggerInfo.Host = cfg.Swagger.Host
if err := run(cfg); err != nil {
panic(err)
}
@ -75,17 +60,17 @@ func run(cfg *config.Config) error {
// =========================================================================
// Initialize Database & Repos
err := os.MkdirAll(cfg.Storage.Data, 0o755)
err := os.MkdirAll(cfg.Storage.Data, 0755)
if err != nil {
log.Fatal().Err(err).Msg("failed to create data directory")
}
c, err := ent.Open("sqlite3", cfg.Storage.SqliteURL)
c, err := ent.Open("sqlite3", cfg.Storage.SqliteUrl)
if err != nil {
log.Fatal().
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL).
Str("url", cfg.Storage.SqliteUrl).
Msg("failed opening connection to sqlite")
}
defer func(c *ent.Client) {
@ -118,7 +103,7 @@ func run(cfg *config.Config) error {
log.Fatal().
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL).
Str("url", cfg.Storage.SqliteUrl).
Msg("failed creating schema resources")
}
@ -128,145 +113,36 @@ func run(cfg *config.Config) error {
return err
}
collectFuncs := []currencies.CollectorFunc{
currencies.CollectDefaults(),
}
if cfg.Options.CurrencyConfig != "" {
log.Info().
Str("path", cfg.Options.CurrencyConfig).
Msg("loading currency config file")
content, err := os.ReadFile(cfg.Options.CurrencyConfig)
if err != nil {
log.Fatal().
Err(err).
Str("path", cfg.Options.CurrencyConfig).
Msg("failed to read currency config file")
}
collectFuncs = append(collectFuncs, currencies.CollectJSON(bytes.NewReader(content)))
}
currencies, err := currencies.CollectionCurrencies(collectFuncs...)
if err != nil {
log.Fatal().
Err(err).
Msg("failed to collect currencies")
}
app.bus = eventbus.New()
app.db = c
app.repos = repo.New(c, app.bus, cfg.Storage.Data)
app.services = services.New(
app.repos,
services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID),
services.WithCurrencies(currencies),
)
app.repos = repo.New(c, cfg.Storage.Data)
app.services = services.NewServices(app.repos)
// =========================================================================
// Start Server
logger := log.With().Caller().Logger()
router := chi.NewMux()
router.Use(
middleware.RequestID,
middleware.RealIP,
mid.Logger(logger),
middleware.Recoverer,
middleware.StripSlashes,
app.server = server.NewServer(
server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port),
)
chain := errchain.New(mid.Errors(logger))
routes := app.newRouter(app.repos)
app.mountRoutes(router, chain, app.repos)
runner := graceful.NewRunner()
runner.AddFunc("server", func(ctx context.Context) error {
httpserver := http.Server{
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Web.Port),
Handler: router,
ReadTimeout: cfg.Web.ReadTimeout,
WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
if app.conf.Mode != config.ModeDevelopment {
app.logRoutes(routes)
}
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()
})
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
// =========================================================================
// Start Reoccurring Tasks
runner.AddFunc("eventbus", app.bus.Run)
runner.AddFunc("seed_database", func(ctx context.Context) error {
// TODO: Remove through external API that does setup
if cfg.Demo {
log.Info().Msg("Running in demo mode, creating demo data")
app.SetupDemo()
}
return nil
})
runner.AddPlugin(NewTask("purge-tokens", time.Duration(24)*time.Hour, func(ctx context.Context) {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(ctx)
go app.startBgTask(time.Duration(24)*time.Hour, func() {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background())
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired tokens")
}
}))
runner.AddPlugin(NewTask("purge-invitations", time.Duration(24)*time.Hour, func(ctx context.Context) {
_, err := app.repos.Groups.InvitationPurge(ctx)
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired invitations")
}
}))
runner.AddPlugin(NewTask("send-notifications", time.Duration(1)*time.Hour, func(ctx context.Context) {
now := time.Now()
if now.Hour() == 8 {
fmt.Println("run notifiers")
err := app.services.BackgroundService.SendNotifiersToday(context.Background())
if err != nil {
log.Error().
Err(err).
Msg("failed to send notifiers")
}
}
}))
if cfg.Debug.Enabled {
runner.AddFunc("debug", func(ctx context.Context) error {
debugserver := http.Server{
Addr: fmt.Sprintf("%s:%s", cfg.Web.Host, cfg.Debug.Port),
Handler: app.debugRouter(),
ReadTimeout: cfg.Web.ReadTimeout,
WriteTimeout: cfg.Web.WriteTimeout,
IdleTimeout: cfg.Web.IdleTimeout,
}
go func() {
<-ctx.Done()
_ = debugserver.Shutdown(context.Background())
}()
log.Info().Msgf("Debug server is running on %s:%s", cfg.Web.Host, cfg.Debug.Port)
return debugserver.ListenAndServe()
})
}
return runner.Start(context.Background())
return app.server.Start(routes)
}

View file

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

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,12 +7,11 @@ import (
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/item"
)
// Attachment is the model entity for the Attachment schema.
@ -26,14 +25,11 @@ type Attachment struct {
UpdatedAt time.Time `json:"updated_at,omitempty"`
// Type holds the value of the "type" field.
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.
// The values are being populated by the AttachmentQuery when eager-loading is set.
Edges AttachmentEdges `json:"edges"`
document_attachments *uuid.UUID
item_attachments *uuid.UUID
selectValues sql.SelectValues
}
// 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))
for i := range columns {
switch columns[i] {
case attachment.FieldPrimary:
values[i] = new(sql.NullBool)
case attachment.FieldType:
values[i] = new(sql.NullString)
case attachment.FieldCreatedAt, attachment.FieldUpdatedAt:
@ -91,7 +85,7 @@ func (*Attachment) scanValues(columns []string) ([]any, error) {
case attachment.ForeignKeys[1]: // item_attachments
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
values[i] = new(sql.UnknownType)
return nil, fmt.Errorf("unexpected column %q for type Attachment", columns[i])
}
}
return values, nil
@ -129,12 +123,6 @@ func (a *Attachment) assignValues(columns []string, values []any) error {
} else if value.Valid {
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]:
if value, ok := values[i].(*sql.NullScanner); !ok {
return fmt.Errorf("unexpected type %T for field document_attachments", values[i])
@ -149,34 +137,26 @@ func (a *Attachment) assignValues(columns []string, values []any) error {
a.item_attachments = new(uuid.UUID)
*a.item_attachments = *value.S.(*uuid.UUID)
}
default:
a.selectValues.Set(columns[i], values[i])
}
}
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.
func (a *Attachment) QueryItem() *ItemQuery {
return NewAttachmentClient(a.config).QueryItem(a)
return (&AttachmentClient{config: a.config}).QueryItem(a)
}
// QueryDocument queries the "document" edge of the Attachment entity.
func (a *Attachment) QueryDocument() *DocumentQuery {
return NewAttachmentClient(a.config).QueryDocument(a)
return (&AttachmentClient{config: a.config}).QueryDocument(a)
}
// Update returns a builder for updating this Attachment.
// Note that you need to call Attachment.Unwrap() before calling this method if this Attachment
// was returned from a transaction, and the transaction was committed or rolled back.
func (a *Attachment) Update() *AttachmentUpdateOne {
return NewAttachmentClient(a.config).UpdateOne(a)
return (&AttachmentClient{config: a.config}).UpdateOne(a)
}
// Unwrap unwraps the Attachment entity that was returned from a transaction after it was closed,
@ -203,12 +183,15 @@ func (a *Attachment) String() string {
builder.WriteString(", ")
builder.WriteString("type=")
builder.WriteString(fmt.Sprintf("%v", a.Type))
builder.WriteString(", ")
builder.WriteString("primary=")
builder.WriteString(fmt.Sprintf("%v", a.Primary))
builder.WriteByte(')')
return builder.String()
}
// Attachments is a parsable slice of Attachment.
type Attachments []*Attachment
func (a Attachments) config(cfg config) {
for _i := range a {
a[_i].config = cfg
}
}

View file

@ -6,8 +6,6 @@ import (
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
)
@ -22,8 +20,6 @@ const (
FieldUpdatedAt = "updated_at"
// FieldType holds the string denoting the type field in the database.
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 = "item"
// EdgeDocument holds the string denoting the document edge name in mutations.
@ -52,7 +48,6 @@ var Columns = []string{
FieldCreatedAt,
FieldUpdatedAt,
FieldType,
FieldPrimary,
}
// ForeignKeys holds the SQL foreign-keys that are owned by the "attachments"
@ -84,8 +79,6 @@ var (
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
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 func() uuid.UUID
)
@ -102,7 +95,6 @@ const (
TypeManual Type = "manual"
TypeWarranty Type = "warranty"
TypeAttachment Type = "attachment"
TypeReceipt Type = "receipt"
)
func (_type Type) String() string {
@ -112,65 +104,9 @@ func (_type Type) String() string {
// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save.
func TypeValidator(_type Type) error {
switch _type {
case TypePhoto, TypeManual, TypeWarranty, TypeAttachment, TypeReceipt:
case TypePhoto, TypeManual, TypeWarranty, TypeAttachment:
return nil
default:
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

@ -8,177 +8,256 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldID, id))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldID, id))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldID, id))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldID), id))
})
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldIn(FieldID, ids...))
return predicate.Attachment(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.In(s.C(FieldID), v...))
})
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldNotIn(FieldID, ids...))
return predicate.Attachment(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.NotIn(s.C(FieldID), v...))
})
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldGT(FieldID, id))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldID), id))
})
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldGTE(FieldID, id))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldID), id))
})
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldLT(FieldID, id))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldID), id))
})
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.Attachment {
return predicate.Attachment(sql.FieldLTE(FieldID, id))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldID), id))
})
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldCreatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.Attachment {
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))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldCreatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldCreatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldIn(FieldCreatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldNotIn(FieldCreatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldGT(FieldCreatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldGTE(FieldCreatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldLT(FieldCreatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldLTE(FieldCreatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldCreatedAt), v))
})
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldUpdatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldUpdatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldIn(FieldUpdatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldNotIn(FieldUpdatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldGT(FieldUpdatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldGTE(FieldUpdatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldLT(FieldUpdatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.Attachment {
return predicate.Attachment(sql.FieldLTE(FieldUpdatedAt, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldUpdatedAt), v))
})
}
// TypeEQ applies the EQ predicate on the "type" field.
func TypeEQ(v Type) predicate.Attachment {
return predicate.Attachment(sql.FieldEQ(FieldType, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldType), v))
})
}
// TypeNEQ applies the NEQ predicate on the "type" field.
func TypeNEQ(v Type) predicate.Attachment {
return predicate.Attachment(sql.FieldNEQ(FieldType, v))
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldType), v))
})
}
// TypeIn applies the In predicate on the "type" field.
func TypeIn(vs ...Type) predicate.Attachment {
return predicate.Attachment(sql.FieldIn(FieldType, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldType), v...))
})
}
// TypeNotIn applies the NotIn predicate on the "type" field.
func TypeNotIn(vs ...Type) predicate.Attachment {
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))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Attachment(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldType), v...))
})
}
// HasItem applies the HasEdge predicate on the "item" edge.
@ -186,6 +265,7 @@ func HasItem() predicate.Attachment {
return predicate.Attachment(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ItemTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ItemTable, ItemColumn),
)
sqlgraph.HasNeighbors(s, step)
@ -195,7 +275,11 @@ func HasItem() predicate.Attachment {
// HasItemWith applies the HasEdge predicate on the "item" edge with a given conditions (other predicates).
func HasItemWith(preds ...predicate.Item) predicate.Attachment {
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) {
for _, p := range preds {
p(s)
@ -209,6 +293,7 @@ func HasDocument() predicate.Attachment {
return predicate.Attachment(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, DocumentTable, DocumentColumn),
)
sqlgraph.HasNeighbors(s, step)
@ -218,7 +303,11 @@ func HasDocument() predicate.Attachment {
// HasDocumentWith applies the HasEdge predicate on the "document" edge with a given conditions (other predicates).
func HasDocumentWith(preds ...predicate.Document) predicate.Attachment {
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) {
for _, p := range preds {
p(s)
@ -229,15 +318,32 @@ func HasDocumentWith(preds ...predicate.Document) predicate.Attachment {
// And groups predicates with the AND operator between them.
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.
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.
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

@ -11,9 +11,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/item"
)
// AttachmentCreate is the builder for creating a Attachment entity.
@ -65,20 +65,6 @@ func (ac *AttachmentCreate) SetNillableType(a *attachment.Type) *AttachmentCreat
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.
func (ac *AttachmentCreate) SetID(u uuid.UUID) *AttachmentCreate {
ac.mutation.SetID(u)
@ -122,8 +108,50 @@ func (ac *AttachmentCreate) Mutation() *AttachmentMutation {
// Save creates the Attachment in the database.
func (ac *AttachmentCreate) Save(ctx context.Context) (*Attachment, error) {
var (
err error
node *Attachment
)
ac.defaults()
return withHooks(ctx, ac.sqlSave, ac.mutation, ac.hooks)
if len(ac.hooks) == 0 {
if err = ac.check(); err != nil {
return nil, err
}
node, err = ac.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AttachmentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = ac.check(); err != nil {
return nil, err
}
ac.mutation = mutation
if node, err = ac.sqlSave(ctx); err != nil {
return nil, err
}
mutation.id = &node.ID
mutation.done = true
return node, err
})
for i := len(ac.hooks) - 1; i >= 0; i-- {
if ac.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = ac.hooks[i](mut)
}
v, err := mut.Mutate(ctx, ac.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*Attachment)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from AttachmentMutation", v)
}
node = nv
}
return node, err
}
// SaveX calls Save and panics if Save returns an error.
@ -162,10 +190,6 @@ func (ac *AttachmentCreate) defaults() {
v := attachment.DefaultType
ac.mutation.SetType(v)
}
if _, ok := ac.mutation.Primary(); !ok {
v := attachment.DefaultPrimary
ac.mutation.SetPrimary(v)
}
if _, ok := ac.mutation.ID(); !ok {
v := attachment.DefaultID()
ac.mutation.SetID(v)
@ -188,9 +212,6 @@ func (ac *AttachmentCreate) check() error {
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 {
return &ValidationError{Name: "item", err: errors.New(`ent: missing required edge "Attachment.item"`)}
}
@ -201,9 +222,6 @@ func (ac *AttachmentCreate) check() error {
}
func (ac *AttachmentCreate) sqlSave(ctx context.Context) (*Attachment, error) {
if err := ac.check(); err != nil {
return nil, err
}
_node, _spec := ac.createSpec()
if err := sqlgraph.CreateNode(ctx, ac.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
@ -218,36 +236,48 @@ func (ac *AttachmentCreate) sqlSave(ctx context.Context) (*Attachment, error) {
return nil, err
}
}
ac.mutation.id = &_node.ID
ac.mutation.done = true
return _node, nil
}
func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
var (
_node = &Attachment{config: ac.config}
_spec = sqlgraph.NewCreateSpec(attachment.Table, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
_spec = &sqlgraph.CreateSpec{
Table: attachment.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
}
)
if id, ok := ac.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = &id
}
if value, ok := ac.mutation.CreatedAt(); ok {
_spec.SetField(attachment.FieldCreatedAt, field.TypeTime, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: attachment.FieldCreatedAt,
})
_node.CreatedAt = value
}
if value, ok := ac.mutation.UpdatedAt(); ok {
_spec.SetField(attachment.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: attachment.FieldUpdatedAt,
})
_node.UpdatedAt = value
}
if value, ok := ac.mutation.GetType(); ok {
_spec.SetField(attachment.FieldType, field.TypeEnum, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeEnum,
Value: value,
Column: attachment.FieldType,
})
_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 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@ -256,7 +286,10 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
},
}
for _, k := range nodes {
@ -273,7 +306,10 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
for _, k := range nodes {
@ -288,15 +324,11 @@ func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) {
// AttachmentCreateBulk is the builder for creating many Attachment entities in bulk.
type AttachmentCreateBulk struct {
config
err error
builders []*AttachmentCreate
}
// Save creates the Attachment entities in the database.
func (acb *AttachmentCreateBulk) Save(ctx context.Context) ([]*Attachment, error) {
if acb.err != nil {
return nil, acb.err
}
specs := make([]*sqlgraph.CreateSpec, len(acb.builders))
nodes := make([]*Attachment, len(acb.builders))
mutators := make([]Mutator, len(acb.builders))
@ -313,8 +345,8 @@ func (acb *AttachmentCreateBulk) Save(ctx context.Context) ([]*Attachment, error
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, acb.builders[i+1].mutation)
} else {

View file

@ -4,12 +4,13 @@ package ent
import (
"context"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// AttachmentDelete is the builder for deleting a Attachment entity.
@ -27,7 +28,34 @@ func (ad *AttachmentDelete) Where(ps ...predicate.Attachment) *AttachmentDelete
// Exec executes the deletion query and returns how many vertices were deleted.
func (ad *AttachmentDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, ad.sqlExec, ad.mutation, ad.hooks)
var (
err error
affected int
)
if len(ad.hooks) == 0 {
affected, err = ad.sqlExec(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AttachmentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
ad.mutation = mutation
affected, err = ad.sqlExec(ctx)
mutation.done = true
return affected, err
})
for i := len(ad.hooks) - 1; i >= 0; i-- {
if ad.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = ad.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, ad.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// ExecX is like Exec, but panics if an error occurs.
@ -40,7 +68,15 @@ func (ad *AttachmentDelete) ExecX(ctx context.Context) int {
}
func (ad *AttachmentDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(attachment.Table, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
if ps := ad.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@ -52,7 +88,6 @@ func (ad *AttachmentDelete) sqlExec(ctx context.Context) (int, error) {
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
ad.mutation.done = true
return affected, err
}
@ -61,12 +96,6 @@ type AttachmentDeleteOne struct {
ad *AttachmentDelete
}
// Where appends a list predicates to the AttachmentDelete builder.
func (ado *AttachmentDeleteOne) Where(ps ...predicate.Attachment) *AttachmentDeleteOne {
ado.ad.mutation.Where(ps...)
return ado
}
// Exec executes the deletion query.
func (ado *AttachmentDeleteOne) Exec(ctx context.Context) error {
n, err := ado.ad.Exec(ctx)
@ -82,7 +111,5 @@ func (ado *AttachmentDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (ado *AttachmentDeleteOne) ExecX(ctx context.Context) {
if err := ado.Exec(ctx); err != nil {
panic(err)
}
ado.ad.ExecX(ctx)
}

View file

@ -11,18 +11,20 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/item"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// AttachmentQuery is the builder for querying Attachment entities.
type AttachmentQuery struct {
config
ctx *QueryContext
order []attachment.OrderOption
inters []Interceptor
limit *int
offset *int
unique *bool
order []OrderFunc
fields []string
predicates []predicate.Attachment
withItem *ItemQuery
withDocument *DocumentQuery
@ -38,34 +40,34 @@ func (aq *AttachmentQuery) Where(ps ...predicate.Attachment) *AttachmentQuery {
return aq
}
// Limit the number of records to be returned by this query.
// Limit adds a limit step to the query.
func (aq *AttachmentQuery) Limit(limit int) *AttachmentQuery {
aq.ctx.Limit = &limit
aq.limit = &limit
return aq
}
// Offset to start from.
// Offset adds an offset step to the query.
func (aq *AttachmentQuery) Offset(offset int) *AttachmentQuery {
aq.ctx.Offset = &offset
aq.offset = &offset
return aq
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (aq *AttachmentQuery) Unique(unique bool) *AttachmentQuery {
aq.ctx.Unique = &unique
aq.unique = &unique
return aq
}
// Order specifies how the records should be ordered.
func (aq *AttachmentQuery) Order(o ...attachment.OrderOption) *AttachmentQuery {
// Order adds an order step to the query.
func (aq *AttachmentQuery) Order(o ...OrderFunc) *AttachmentQuery {
aq.order = append(aq.order, o...)
return aq
}
// QueryItem chains the current query on the "item" edge.
func (aq *AttachmentQuery) QueryItem() *ItemQuery {
query := (&ItemClient{config: aq.config}).Query()
query := &ItemQuery{config: aq.config}
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := aq.prepareQuery(ctx); err != nil {
return nil, err
@ -87,7 +89,7 @@ func (aq *AttachmentQuery) QueryItem() *ItemQuery {
// QueryDocument chains the current query on the "document" edge.
func (aq *AttachmentQuery) QueryDocument() *DocumentQuery {
query := (&DocumentClient{config: aq.config}).Query()
query := &DocumentQuery{config: aq.config}
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := aq.prepareQuery(ctx); err != nil {
return nil, err
@ -110,7 +112,7 @@ func (aq *AttachmentQuery) QueryDocument() *DocumentQuery {
// First returns the first Attachment entity from the query.
// Returns a *NotFoundError when no Attachment was found.
func (aq *AttachmentQuery) First(ctx context.Context) (*Attachment, error) {
nodes, err := aq.Limit(1).All(setContextOp(ctx, aq.ctx, "First"))
nodes, err := aq.Limit(1).All(ctx)
if err != nil {
return nil, err
}
@ -133,7 +135,7 @@ func (aq *AttachmentQuery) FirstX(ctx context.Context) *Attachment {
// Returns a *NotFoundError when no Attachment ID was found.
func (aq *AttachmentQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = aq.Limit(1).IDs(setContextOp(ctx, aq.ctx, "FirstID")); err != nil {
if ids, err = aq.Limit(1).IDs(ctx); err != nil {
return
}
if len(ids) == 0 {
@ -156,7 +158,7 @@ func (aq *AttachmentQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one Attachment entity is found.
// Returns a *NotFoundError when no Attachment entities are found.
func (aq *AttachmentQuery) Only(ctx context.Context) (*Attachment, error) {
nodes, err := aq.Limit(2).All(setContextOp(ctx, aq.ctx, "Only"))
nodes, err := aq.Limit(2).All(ctx)
if err != nil {
return nil, err
}
@ -184,7 +186,7 @@ func (aq *AttachmentQuery) OnlyX(ctx context.Context) *Attachment {
// Returns a *NotFoundError when no entities are found.
func (aq *AttachmentQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = aq.Limit(2).IDs(setContextOp(ctx, aq.ctx, "OnlyID")); err != nil {
if ids, err = aq.Limit(2).IDs(ctx); err != nil {
return
}
switch len(ids) {
@ -209,12 +211,10 @@ func (aq *AttachmentQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of Attachments.
func (aq *AttachmentQuery) All(ctx context.Context) ([]*Attachment, error) {
ctx = setContextOp(ctx, aq.ctx, "All")
if err := aq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Attachment, *AttachmentQuery]()
return withInterceptors[[]*Attachment](ctx, aq, qr, aq.inters)
return aq.sqlAll(ctx)
}
// AllX is like All, but panics if an error occurs.
@ -227,12 +227,9 @@ func (aq *AttachmentQuery) AllX(ctx context.Context) []*Attachment {
}
// IDs executes the query and returns a list of Attachment IDs.
func (aq *AttachmentQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if aq.ctx.Unique == nil && aq.path != nil {
aq.Unique(true)
}
ctx = setContextOp(ctx, aq.ctx, "IDs")
if err = aq.Select(attachment.FieldID).Scan(ctx, &ids); err != nil {
func (aq *AttachmentQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
if err := aq.Select(attachment.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@ -249,11 +246,10 @@ func (aq *AttachmentQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (aq *AttachmentQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, aq.ctx, "Count")
if err := aq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, aq, querierCount[*AttachmentQuery](), aq.inters)
return aq.sqlCount(ctx)
}
// CountX is like Count, but panics if an error occurs.
@ -267,15 +263,10 @@ func (aq *AttachmentQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (aq *AttachmentQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, aq.ctx, "Exist")
switch _, err := aq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
if err := aq.prepareQuery(ctx); err != nil {
return false, err
}
return aq.sqlExist(ctx)
}
// ExistX is like Exist, but panics if an error occurs.
@ -295,22 +286,23 @@ func (aq *AttachmentQuery) Clone() *AttachmentQuery {
}
return &AttachmentQuery{
config: aq.config,
ctx: aq.ctx.Clone(),
order: append([]attachment.OrderOption{}, aq.order...),
inters: append([]Interceptor{}, aq.inters...),
limit: aq.limit,
offset: aq.offset,
order: append([]OrderFunc{}, aq.order...),
predicates: append([]predicate.Attachment{}, aq.predicates...),
withItem: aq.withItem.Clone(),
withDocument: aq.withDocument.Clone(),
// clone intermediate query.
sql: aq.sql.Clone(),
path: aq.path,
unique: aq.unique,
}
}
// WithItem tells the query-builder to eager-load the nodes that are connected to
// the "item" edge. The optional arguments are used to configure the query builder of the edge.
func (aq *AttachmentQuery) WithItem(opts ...func(*ItemQuery)) *AttachmentQuery {
query := (&ItemClient{config: aq.config}).Query()
query := &ItemQuery{config: aq.config}
for _, opt := range opts {
opt(query)
}
@ -321,7 +313,7 @@ func (aq *AttachmentQuery) WithItem(opts ...func(*ItemQuery)) *AttachmentQuery {
// WithDocument tells the query-builder to eager-load the nodes that are connected to
// the "document" edge. The optional arguments are used to configure the query builder of the edge.
func (aq *AttachmentQuery) WithDocument(opts ...func(*DocumentQuery)) *AttachmentQuery {
query := (&DocumentClient{config: aq.config}).Query()
query := &DocumentQuery{config: aq.config}
for _, opt := range opts {
opt(query)
}
@ -344,11 +336,16 @@ func (aq *AttachmentQuery) WithDocument(opts ...func(*DocumentQuery)) *Attachmen
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (aq *AttachmentQuery) GroupBy(field string, fields ...string) *AttachmentGroupBy {
aq.ctx.Fields = append([]string{field}, fields...)
grbuild := &AttachmentGroupBy{build: aq}
grbuild.flds = &aq.ctx.Fields
grbuild := &AttachmentGroupBy{config: aq.config}
grbuild.fields = append([]string{field}, fields...)
grbuild.path = func(ctx context.Context) (prev *sql.Selector, err error) {
if err := aq.prepareQuery(ctx); err != nil {
return nil, err
}
return aq.sqlQuery(ctx), nil
}
grbuild.label = attachment.Label
grbuild.scan = grbuild.Scan
grbuild.flds, grbuild.scan = &grbuild.fields, grbuild.Scan
return grbuild
}
@ -365,30 +362,15 @@ func (aq *AttachmentQuery) GroupBy(field string, fields ...string) *AttachmentGr
// Select(attachment.FieldCreatedAt).
// Scan(ctx, &v)
func (aq *AttachmentQuery) Select(fields ...string) *AttachmentSelect {
aq.ctx.Fields = append(aq.ctx.Fields, fields...)
sbuild := &AttachmentSelect{AttachmentQuery: aq}
sbuild.label = attachment.Label
sbuild.flds, sbuild.scan = &aq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AttachmentSelect configured with the given aggregations.
func (aq *AttachmentQuery) Aggregate(fns ...AggregateFunc) *AttachmentSelect {
return aq.Select().Aggregate(fns...)
aq.fields = append(aq.fields, fields...)
selbuild := &AttachmentSelect{AttachmentQuery: aq}
selbuild.label = attachment.Label
selbuild.flds, selbuild.scan = &aq.fields, selbuild.Scan
return selbuild
}
func (aq *AttachmentQuery) prepareQuery(ctx context.Context) error {
for _, inter := range aq.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, aq); err != nil {
return err
}
}
}
for _, f := range aq.ctx.Fields {
for _, f := range aq.fields {
if !attachment.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@ -465,9 +447,6 @@ func (aq *AttachmentQuery) loadItem(ctx context.Context, query *ItemQuery, nodes
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(item.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@ -497,9 +476,6 @@ func (aq *AttachmentQuery) loadDocument(ctx context.Context, query *DocumentQuer
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(document.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@ -519,22 +495,41 @@ func (aq *AttachmentQuery) loadDocument(ctx context.Context, query *DocumentQuer
func (aq *AttachmentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := aq.querySpec()
_spec.Node.Columns = aq.ctx.Fields
if len(aq.ctx.Fields) > 0 {
_spec.Unique = aq.ctx.Unique != nil && *aq.ctx.Unique
_spec.Node.Columns = aq.fields
if len(aq.fields) > 0 {
_spec.Unique = aq.unique != nil && *aq.unique
}
return sqlgraph.CountNodes(ctx, aq.driver, _spec)
}
func (aq *AttachmentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
_spec.From = aq.sql
if unique := aq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if aq.path != nil {
_spec.Unique = true
func (aq *AttachmentQuery) sqlExist(ctx context.Context) (bool, error) {
switch _, err := aq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
if fields := aq.ctx.Fields; len(fields) > 0 {
}
func (aq *AttachmentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
Columns: attachment.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
From: aq.sql,
Unique: true,
}
if unique := aq.unique; unique != nil {
_spec.Unique = *unique
}
if fields := aq.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, attachment.FieldID)
for i := range fields {
@ -550,10 +545,10 @@ func (aq *AttachmentQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := aq.ctx.Limit; limit != nil {
if limit := aq.limit; limit != nil {
_spec.Limit = *limit
}
if offset := aq.ctx.Offset; offset != nil {
if offset := aq.offset; offset != nil {
_spec.Offset = *offset
}
if ps := aq.order; len(ps) > 0 {
@ -569,7 +564,7 @@ func (aq *AttachmentQuery) querySpec() *sqlgraph.QuerySpec {
func (aq *AttachmentQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(aq.driver.Dialect())
t1 := builder.Table(attachment.Table)
columns := aq.ctx.Fields
columns := aq.fields
if len(columns) == 0 {
columns = attachment.Columns
}
@ -578,7 +573,7 @@ func (aq *AttachmentQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = aq.sql
selector.Select(selector.Columns(columns...)...)
}
if aq.ctx.Unique != nil && *aq.ctx.Unique {
if aq.unique != nil && *aq.unique {
selector.Distinct()
}
for _, p := range aq.predicates {
@ -587,12 +582,12 @@ func (aq *AttachmentQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range aq.order {
p(selector)
}
if offset := aq.ctx.Offset; offset != nil {
if offset := aq.offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := aq.ctx.Limit; limit != nil {
if limit := aq.limit; limit != nil {
selector.Limit(*limit)
}
return selector
@ -600,8 +595,13 @@ func (aq *AttachmentQuery) sqlQuery(ctx context.Context) *sql.Selector {
// AttachmentGroupBy is the group-by builder for Attachment entities.
type AttachmentGroupBy struct {
config
selector
build *AttachmentQuery
fields []string
fns []AggregateFunc
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Aggregate adds the given aggregation functions to the group-by query.
@ -610,77 +610,74 @@ func (agb *AttachmentGroupBy) Aggregate(fns ...AggregateFunc) *AttachmentGroupBy
return agb
}
// Scan applies the selector query and scans the result into the given value.
// Scan applies the group-by query and scans the result into the given value.
func (agb *AttachmentGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, agb.build.ctx, "GroupBy")
if err := agb.build.prepareQuery(ctx); err != nil {
query, err := agb.path(ctx)
if err != nil {
return err
}
return scanWithInterceptors[*AttachmentQuery, *AttachmentGroupBy](ctx, agb.build, agb, agb.build.inters, v)
agb.sql = query
return agb.sqlScan(ctx, v)
}
func (agb *AttachmentGroupBy) sqlScan(ctx context.Context, root *AttachmentQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(agb.fns))
for _, fn := range agb.fns {
aggregation = append(aggregation, fn(selector))
func (agb *AttachmentGroupBy) sqlScan(ctx context.Context, v any) error {
for _, f := range agb.fields {
if !attachment.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)}
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*agb.flds)+len(agb.fns))
for _, f := range *agb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*agb.flds...)...)
selector := agb.sqlQuery()
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := agb.build.driver.Query(ctx, query, args, rows); err != nil {
if err := agb.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
func (agb *AttachmentGroupBy) sqlQuery() *sql.Selector {
selector := agb.sql.Select()
aggregation := make([]string, 0, len(agb.fns))
for _, fn := range agb.fns {
aggregation = append(aggregation, fn(selector))
}
// If no columns were selected in a custom aggregation function, the default
// selection is the fields used for "group-by", and the aggregation functions.
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(agb.fields)+len(agb.fns))
for _, f := range agb.fields {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
return selector.GroupBy(selector.Columns(agb.fields...)...)
}
// AttachmentSelect is the builder for selecting fields of Attachment entities.
type AttachmentSelect struct {
*AttachmentQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (as *AttachmentSelect) Aggregate(fns ...AggregateFunc) *AttachmentSelect {
as.fns = append(as.fns, fns...)
return as
// intermediate query (i.e. traversal path).
sql *sql.Selector
}
// Scan applies the selector query and scans the result into the given value.
func (as *AttachmentSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, as.ctx, "Select")
if err := as.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*AttachmentQuery, *AttachmentSelect](ctx, as.AttachmentQuery, as, as.inters, v)
as.sql = as.AttachmentQuery.sqlQuery(ctx)
return as.sqlScan(ctx, v)
}
func (as *AttachmentSelect) sqlScan(ctx context.Context, root *AttachmentQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(as.fns))
for _, fn := range as.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*as.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
func (as *AttachmentSelect) sqlScan(ctx context.Context, v any) error {
rows := &sql.Rows{}
query, args := selector.Query()
query, args := as.sql.Query()
if err := as.driver.Query(ctx, query, args, rows); err != nil {
return err
}

View file

@ -12,10 +12,10 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/item"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// AttachmentUpdate is the builder for updating Attachment entities.
@ -51,20 +51,6 @@ func (au *AttachmentUpdate) SetNillableType(a *attachment.Type) *AttachmentUpdat
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.
func (au *AttachmentUpdate) SetItemID(id uuid.UUID) *AttachmentUpdate {
au.mutation.SetItemID(id)
@ -106,8 +92,41 @@ func (au *AttachmentUpdate) ClearDocument() *AttachmentUpdate {
// Save executes the query and returns the number of nodes affected by the update operation.
func (au *AttachmentUpdate) Save(ctx context.Context) (int, error) {
var (
err error
affected int
)
au.defaults()
return withHooks(ctx, au.sqlSave, au.mutation, au.hooks)
if len(au.hooks) == 0 {
if err = au.check(); err != nil {
return 0, err
}
affected, err = au.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AttachmentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = au.check(); err != nil {
return 0, err
}
au.mutation = mutation
affected, err = au.sqlSave(ctx)
mutation.done = true
return affected, err
})
for i := len(au.hooks) - 1; i >= 0; i-- {
if au.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = au.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, au.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// SaveX is like Save, but panics if an error occurs.
@ -157,10 +176,16 @@ func (au *AttachmentUpdate) check() error {
}
func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := au.check(); err != nil {
return n, err
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
Columns: attachment.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
if ps := au.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@ -169,13 +194,18 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
}
if value, ok := au.mutation.UpdatedAt(); ok {
_spec.SetField(attachment.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: attachment.FieldUpdatedAt,
})
}
if value, ok := au.mutation.GetType(); ok {
_spec.SetField(attachment.FieldType, field.TypeEnum, value)
}
if value, ok := au.mutation.Primary(); ok {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeEnum,
Value: value,
Column: attachment.FieldType,
})
}
if au.mutation.ItemCleared() {
edge := &sqlgraph.EdgeSpec{
@ -185,7 +215,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.ItemColumn},
Bidi: false,
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)
@ -198,7 +231,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
},
}
for _, k := range nodes {
@ -214,7 +250,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.DocumentColumn},
Bidi: false,
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)
@ -227,7 +266,10 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
for _, k := range nodes {
@ -243,7 +285,6 @@ func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
return 0, err
}
au.mutation.done = true
return n, nil
}
@ -275,20 +316,6 @@ func (auo *AttachmentUpdateOne) SetNillableType(a *attachment.Type) *AttachmentU
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.
func (auo *AttachmentUpdateOne) SetItemID(id uuid.UUID) *AttachmentUpdateOne {
auo.mutation.SetItemID(id)
@ -328,12 +355,6 @@ func (auo *AttachmentUpdateOne) ClearDocument() *AttachmentUpdateOne {
return auo
}
// Where appends a list predicates to the AttachmentUpdate builder.
func (auo *AttachmentUpdateOne) Where(ps ...predicate.Attachment) *AttachmentUpdateOne {
auo.mutation.Where(ps...)
return auo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (auo *AttachmentUpdateOne) Select(field string, fields ...string) *AttachmentUpdateOne {
@ -343,8 +364,47 @@ func (auo *AttachmentUpdateOne) Select(field string, fields ...string) *Attachme
// Save executes the query and returns the updated Attachment entity.
func (auo *AttachmentUpdateOne) Save(ctx context.Context) (*Attachment, error) {
var (
err error
node *Attachment
)
auo.defaults()
return withHooks(ctx, auo.sqlSave, auo.mutation, auo.hooks)
if len(auo.hooks) == 0 {
if err = auo.check(); err != nil {
return nil, err
}
node, err = auo.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AttachmentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = auo.check(); err != nil {
return nil, err
}
auo.mutation = mutation
node, err = auo.sqlSave(ctx)
mutation.done = true
return node, err
})
for i := len(auo.hooks) - 1; i >= 0; i-- {
if auo.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = auo.hooks[i](mut)
}
v, err := mut.Mutate(ctx, auo.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*Attachment)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from AttachmentMutation", v)
}
node = nv
}
return node, err
}
// SaveX is like Save, but panics if an error occurs.
@ -394,10 +454,16 @@ func (auo *AttachmentUpdateOne) check() error {
}
func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment, err error) {
if err := auo.check(); err != nil {
return _node, err
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: attachment.Table,
Columns: attachment.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID))
id, ok := auo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Attachment.id" for update`)}
@ -423,13 +489,18 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
}
}
if value, ok := auo.mutation.UpdatedAt(); ok {
_spec.SetField(attachment.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: attachment.FieldUpdatedAt,
})
}
if value, ok := auo.mutation.GetType(); ok {
_spec.SetField(attachment.FieldType, field.TypeEnum, value)
}
if value, ok := auo.mutation.Primary(); ok {
_spec.SetField(attachment.FieldPrimary, field.TypeBool, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeEnum,
Value: value,
Column: attachment.FieldType,
})
}
if auo.mutation.ItemCleared() {
edge := &sqlgraph.EdgeSpec{
@ -439,7 +510,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.ItemColumn},
Bidi: false,
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)
@ -452,7 +526,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.ItemColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(item.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
},
}
for _, k := range nodes {
@ -468,7 +545,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
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)
@ -481,7 +561,10 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
Columns: []string{attachment.DocumentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
for _, k := range nodes {
@ -500,6 +583,5 @@ func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment,
}
return nil, err
}
auo.mutation.done = true
return _node, nil
}

View file

@ -7,12 +7,10 @@ import (
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"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/user"
"github.com/hay-kot/homebox/backend/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/user"
)
// AuthTokens is the model entity for the AuthTokens schema.
@ -32,18 +30,15 @@ type AuthTokens struct {
// The values are being populated by the AuthTokensQuery when eager-loading is set.
Edges AuthTokensEdges `json:"edges"`
user_auth_tokens *uuid.UUID
selectValues sql.SelectValues
}
// AuthTokensEdges holds the relations/edges for other nodes in the graph.
type AuthTokensEdges struct {
// User holds the value of the user edge.
User *User `json:"user,omitempty"`
// Roles holds the value of the roles edge.
Roles *AuthRoles `json:"roles,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [2]bool
loadedTypes [1]bool
}
// UserOrErr returns the User value or an error if the edge
@ -59,19 +54,6 @@ func (e AuthTokensEdges) UserOrErr() (*User, error) {
return nil, &NotLoadedError{edge: "user"}
}
// RolesOrErr returns the Roles value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AuthTokensEdges) RolesOrErr() (*AuthRoles, error) {
if e.loadedTypes[1] {
if e.Roles == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: authroles.Label}
}
return e.Roles, nil
}
return nil, &NotLoadedError{edge: "roles"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*AuthTokens) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
@ -86,7 +68,7 @@ func (*AuthTokens) scanValues(columns []string) ([]any, error) {
case authtokens.ForeignKeys[0]: // user_auth_tokens
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
values[i] = new(sql.UnknownType)
return nil, fmt.Errorf("unexpected column %q for type AuthTokens", columns[i])
}
}
return values, nil
@ -137,34 +119,21 @@ func (at *AuthTokens) assignValues(columns []string, values []any) error {
at.user_auth_tokens = new(uuid.UUID)
*at.user_auth_tokens = *value.S.(*uuid.UUID)
}
default:
at.selectValues.Set(columns[i], values[i])
}
}
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.
func (at *AuthTokens) QueryUser() *UserQuery {
return NewAuthTokensClient(at.config).QueryUser(at)
}
// QueryRoles queries the "roles" edge of the AuthTokens entity.
func (at *AuthTokens) QueryRoles() *AuthRolesQuery {
return NewAuthTokensClient(at.config).QueryRoles(at)
return (&AuthTokensClient{config: at.config}).QueryUser(at)
}
// Update returns a builder for updating this AuthTokens.
// Note that you need to call AuthTokens.Unwrap() before calling this method if this AuthTokens
// was returned from a transaction, and the transaction was committed or rolled back.
func (at *AuthTokens) Update() *AuthTokensUpdateOne {
return NewAuthTokensClient(at.config).UpdateOne(at)
return (&AuthTokensClient{config: at.config}).UpdateOne(at)
}
// Unwrap unwraps the AuthTokens entity that was returned from a transaction after it was closed,
@ -200,3 +169,9 @@ func (at *AuthTokens) String() string {
// AuthTokensSlice is a parsable slice of AuthTokens.
type AuthTokensSlice []*AuthTokens
func (at AuthTokensSlice) config(cfg config) {
for _i := range at {
at[_i].config = cfg
}
}

View file

@ -5,8 +5,6 @@ package authtokens
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
)
@ -25,8 +23,6 @@ const (
FieldExpiresAt = "expires_at"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// EdgeRoles holds the string denoting the roles edge name in mutations.
EdgeRoles = "roles"
// Table holds the table name of the authtokens in the database.
Table = "auth_tokens"
// UserTable is the table that holds the user relation/edge.
@ -36,13 +32,6 @@ const (
UserInverseTable = "users"
// UserColumn is the table column denoting the user relation/edge.
UserColumn = "user_auth_tokens"
// RolesTable is the table that holds the roles relation/edge.
RolesTable = "auth_roles"
// RolesInverseTable is the table name for the AuthRoles entity.
// It exists in this package in order to avoid circular dependency with the "authroles" package.
RolesInverseTable = "auth_roles"
// RolesColumn is the table column denoting the roles relation/edge.
RolesColumn = "auth_tokens_roles"
)
// Columns holds all SQL columns for authtokens fields.
@ -87,54 +76,3 @@ var (
// DefaultID holds the default value on creation for the "id" field.
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

@ -8,232 +8,362 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldID, id))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldID, id))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNEQ(FieldID, id))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldID), id))
})
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldIn(FieldID, ids...))
return predicate.AuthTokens(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.In(s.C(FieldID), v...))
})
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNotIn(FieldID, ids...))
return predicate.AuthTokens(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.NotIn(s.C(FieldID), v...))
})
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGT(FieldID, id))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldID), id))
})
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGTE(FieldID, id))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldID), id))
})
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLT(FieldID, id))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldID), id))
})
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLTE(FieldID, id))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldID), id))
})
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldCreatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldUpdatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// Token applies equality check predicate on the "token" field. It's identical to TokenEQ.
func Token(v []byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldToken, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldToken), v))
})
}
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
func ExpiresAt(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldExpiresAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldExpiresAt), v))
})
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldCreatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNEQ(FieldCreatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldIn(FieldCreatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNotIn(FieldCreatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGT(FieldCreatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGTE(FieldCreatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLT(FieldCreatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLTE(FieldCreatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldCreatedAt), v))
})
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldUpdatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNEQ(FieldUpdatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldIn(FieldUpdatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNotIn(FieldUpdatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGT(FieldUpdatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGTE(FieldUpdatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLT(FieldUpdatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLTE(FieldUpdatedAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldUpdatedAt), v))
})
}
// TokenEQ applies the EQ predicate on the "token" field.
func TokenEQ(v []byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldToken, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldToken), v))
})
}
// TokenNEQ applies the NEQ predicate on the "token" field.
func TokenNEQ(v []byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNEQ(FieldToken, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldToken), v))
})
}
// TokenIn applies the In predicate on the "token" field.
func TokenIn(vs ...[]byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldIn(FieldToken, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldToken), v...))
})
}
// TokenNotIn applies the NotIn predicate on the "token" field.
func TokenNotIn(vs ...[]byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNotIn(FieldToken, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldToken), v...))
})
}
// TokenGT applies the GT predicate on the "token" field.
func TokenGT(v []byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGT(FieldToken, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldToken), v))
})
}
// TokenGTE applies the GTE predicate on the "token" field.
func TokenGTE(v []byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGTE(FieldToken, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldToken), v))
})
}
// TokenLT applies the LT predicate on the "token" field.
func TokenLT(v []byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLT(FieldToken, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldToken), v))
})
}
// TokenLTE applies the LTE predicate on the "token" field.
func TokenLTE(v []byte) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLTE(FieldToken, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldToken), v))
})
}
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
func ExpiresAtEQ(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldEQ(FieldExpiresAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
func ExpiresAtNEQ(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNEQ(FieldExpiresAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtIn applies the In predicate on the "expires_at" field.
func ExpiresAtIn(vs ...time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldIn(FieldExpiresAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldExpiresAt), v...))
})
}
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
func ExpiresAtNotIn(vs ...time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldNotIn(FieldExpiresAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldExpiresAt), v...))
})
}
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
func ExpiresAtGT(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGT(FieldExpiresAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
func ExpiresAtGTE(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldGTE(FieldExpiresAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
func ExpiresAtLT(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLT(FieldExpiresAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldExpiresAt), v))
})
}
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
func ExpiresAtLTE(v time.Time) predicate.AuthTokens {
return predicate.AuthTokens(sql.FieldLTE(FieldExpiresAt, v))
return predicate.AuthTokens(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldExpiresAt), v))
})
}
// HasUser applies the HasEdge predicate on the "user" edge.
@ -241,6 +371,7 @@ func HasUser() predicate.AuthTokens {
return predicate.AuthTokens(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
sqlgraph.HasNeighbors(s, step)
@ -249,31 +380,12 @@ func HasUser() predicate.AuthTokens {
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
func HasUserWith(preds ...predicate.User) predicate.AuthTokens {
return predicate.AuthTokens(func(s *sql.Selector) {
step := newUserStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasRoles applies the HasEdge predicate on the "roles" edge.
func HasRoles() predicate.AuthTokens {
return predicate.AuthTokens(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2O, false, RolesTable, RolesColumn),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasRolesWith applies the HasEdge predicate on the "roles" edge with a given conditions (other predicates).
func HasRolesWith(preds ...predicate.AuthRoles) predicate.AuthTokens {
return predicate.AuthTokens(func(s *sql.Selector) {
step := newRolesStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
@ -284,15 +396,32 @@ func HasRolesWith(preds ...predicate.AuthRoles) predicate.AuthTokens {
// And groups predicates with the AND operator between them.
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.
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.
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

@ -11,9 +11,8 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
"github.com/hay-kot/homebox/backend/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/user"
)
// AuthTokensCreate is the builder for creating a AuthTokens entity.
@ -104,25 +103,6 @@ func (atc *AuthTokensCreate) SetUser(u *User) *AuthTokensCreate {
return atc.SetUserID(u.ID)
}
// SetRolesID sets the "roles" edge to the AuthRoles entity by ID.
func (atc *AuthTokensCreate) SetRolesID(id int) *AuthTokensCreate {
atc.mutation.SetRolesID(id)
return atc
}
// SetNillableRolesID sets the "roles" edge to the AuthRoles entity by ID if the given value is not nil.
func (atc *AuthTokensCreate) SetNillableRolesID(id *int) *AuthTokensCreate {
if id != nil {
atc = atc.SetRolesID(*id)
}
return atc
}
// SetRoles sets the "roles" edge to the AuthRoles entity.
func (atc *AuthTokensCreate) SetRoles(a *AuthRoles) *AuthTokensCreate {
return atc.SetRolesID(a.ID)
}
// Mutation returns the AuthTokensMutation object of the builder.
func (atc *AuthTokensCreate) Mutation() *AuthTokensMutation {
return atc.mutation
@ -130,8 +110,50 @@ func (atc *AuthTokensCreate) Mutation() *AuthTokensMutation {
// Save creates the AuthTokens in the database.
func (atc *AuthTokensCreate) Save(ctx context.Context) (*AuthTokens, error) {
var (
err error
node *AuthTokens
)
atc.defaults()
return withHooks(ctx, atc.sqlSave, atc.mutation, atc.hooks)
if len(atc.hooks) == 0 {
if err = atc.check(); err != nil {
return nil, err
}
node, err = atc.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AuthTokensMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = atc.check(); err != nil {
return nil, err
}
atc.mutation = mutation
if node, err = atc.sqlSave(ctx); err != nil {
return nil, err
}
mutation.id = &node.ID
mutation.done = true
return node, err
})
for i := len(atc.hooks) - 1; i >= 0; i-- {
if atc.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = atc.hooks[i](mut)
}
v, err := mut.Mutate(ctx, atc.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*AuthTokens)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from AuthTokensMutation", v)
}
node = nv
}
return node, err
}
// SaveX calls Save and panics if Save returns an error.
@ -194,9 +216,6 @@ func (atc *AuthTokensCreate) check() error {
}
func (atc *AuthTokensCreate) sqlSave(ctx context.Context) (*AuthTokens, error) {
if err := atc.check(); err != nil {
return nil, err
}
_node, _spec := atc.createSpec()
if err := sqlgraph.CreateNode(ctx, atc.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
@ -211,34 +230,54 @@ func (atc *AuthTokensCreate) sqlSave(ctx context.Context) (*AuthTokens, error) {
return nil, err
}
}
atc.mutation.id = &_node.ID
atc.mutation.done = true
return _node, nil
}
func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
var (
_node = &AuthTokens{config: atc.config}
_spec = sqlgraph.NewCreateSpec(authtokens.Table, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
_spec = &sqlgraph.CreateSpec{
Table: authtokens.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
}
)
if id, ok := atc.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = &id
}
if value, ok := atc.mutation.CreatedAt(); ok {
_spec.SetField(authtokens.FieldCreatedAt, field.TypeTime, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: authtokens.FieldCreatedAt,
})
_node.CreatedAt = value
}
if value, ok := atc.mutation.UpdatedAt(); ok {
_spec.SetField(authtokens.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: authtokens.FieldUpdatedAt,
})
_node.UpdatedAt = value
}
if value, ok := atc.mutation.Token(); ok {
_spec.SetField(authtokens.FieldToken, field.TypeBytes, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeBytes,
Value: value,
Column: authtokens.FieldToken,
})
_node.Token = value
}
if value, ok := atc.mutation.ExpiresAt(); ok {
_spec.SetField(authtokens.FieldExpiresAt, field.TypeTime, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: authtokens.FieldExpiresAt,
})
_node.ExpiresAt = value
}
if nodes := atc.mutation.UserIDs(); len(nodes) > 0 {
@ -249,7 +288,10 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
},
}
for _, k := range nodes {
@ -258,37 +300,17 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) {
_node.user_auth_tokens = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := atc.mutation.RolesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2O,
Inverse: false,
Table: authtokens.RolesTable,
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// AuthTokensCreateBulk is the builder for creating many AuthTokens entities in bulk.
type AuthTokensCreateBulk struct {
config
err error
builders []*AuthTokensCreate
}
// Save creates the AuthTokens entities in the database.
func (atcb *AuthTokensCreateBulk) Save(ctx context.Context) ([]*AuthTokens, error) {
if atcb.err != nil {
return nil, atcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(atcb.builders))
nodes := make([]*AuthTokens, len(atcb.builders))
mutators := make([]Mutator, len(atcb.builders))
@ -305,8 +327,8 @@ func (atcb *AuthTokensCreateBulk) Save(ctx context.Context) ([]*AuthTokens, erro
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, atcb.builders[i+1].mutation)
} else {

View file

@ -4,12 +4,13 @@ package ent
import (
"context"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// AuthTokensDelete is the builder for deleting a AuthTokens entity.
@ -27,7 +28,34 @@ func (atd *AuthTokensDelete) Where(ps ...predicate.AuthTokens) *AuthTokensDelete
// Exec executes the deletion query and returns how many vertices were deleted.
func (atd *AuthTokensDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, atd.sqlExec, atd.mutation, atd.hooks)
var (
err error
affected int
)
if len(atd.hooks) == 0 {
affected, err = atd.sqlExec(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AuthTokensMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
atd.mutation = mutation
affected, err = atd.sqlExec(ctx)
mutation.done = true
return affected, err
})
for i := len(atd.hooks) - 1; i >= 0; i-- {
if atd.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = atd.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, atd.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// ExecX is like Exec, but panics if an error occurs.
@ -40,7 +68,15 @@ func (atd *AuthTokensDelete) ExecX(ctx context.Context) int {
}
func (atd *AuthTokensDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(authtokens.Table, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
}
if ps := atd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@ -52,7 +88,6 @@ func (atd *AuthTokensDelete) sqlExec(ctx context.Context) (int, error) {
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
atd.mutation.done = true
return affected, err
}
@ -61,12 +96,6 @@ type AuthTokensDeleteOne struct {
atd *AuthTokensDelete
}
// Where appends a list predicates to the AuthTokensDelete builder.
func (atdo *AuthTokensDeleteOne) Where(ps ...predicate.AuthTokens) *AuthTokensDeleteOne {
atdo.atd.mutation.Where(ps...)
return atdo
}
// Exec executes the deletion query.
func (atdo *AuthTokensDeleteOne) Exec(ctx context.Context) error {
n, err := atdo.atd.Exec(ctx)
@ -82,7 +111,5 @@ func (atdo *AuthTokensDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (atdo *AuthTokensDeleteOne) ExecX(ctx context.Context) {
if err := atdo.Exec(ctx); err != nil {
panic(err)
}
atdo.atd.ExecX(ctx)
}

View file

@ -4,7 +4,6 @@ package ent
import (
"context"
"database/sql/driver"
"fmt"
"math"
@ -12,21 +11,21 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
"github.com/hay-kot/homebox/backend/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/user"
)
// AuthTokensQuery is the builder for querying AuthTokens entities.
type AuthTokensQuery struct {
config
ctx *QueryContext
order []authtokens.OrderOption
inters []Interceptor
limit *int
offset *int
unique *bool
order []OrderFunc
fields []string
predicates []predicate.AuthTokens
withUser *UserQuery
withRoles *AuthRolesQuery
withFKs bool
// intermediate query (i.e. traversal path).
sql *sql.Selector
@ -39,34 +38,34 @@ func (atq *AuthTokensQuery) Where(ps ...predicate.AuthTokens) *AuthTokensQuery {
return atq
}
// Limit the number of records to be returned by this query.
// Limit adds a limit step to the query.
func (atq *AuthTokensQuery) Limit(limit int) *AuthTokensQuery {
atq.ctx.Limit = &limit
atq.limit = &limit
return atq
}
// Offset to start from.
// Offset adds an offset step to the query.
func (atq *AuthTokensQuery) Offset(offset int) *AuthTokensQuery {
atq.ctx.Offset = &offset
atq.offset = &offset
return atq
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (atq *AuthTokensQuery) Unique(unique bool) *AuthTokensQuery {
atq.ctx.Unique = &unique
atq.unique = &unique
return atq
}
// Order specifies how the records should be ordered.
func (atq *AuthTokensQuery) Order(o ...authtokens.OrderOption) *AuthTokensQuery {
// Order adds an order step to the query.
func (atq *AuthTokensQuery) Order(o ...OrderFunc) *AuthTokensQuery {
atq.order = append(atq.order, o...)
return atq
}
// QueryUser chains the current query on the "user" edge.
func (atq *AuthTokensQuery) QueryUser() *UserQuery {
query := (&UserClient{config: atq.config}).Query()
query := &UserQuery{config: atq.config}
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := atq.prepareQuery(ctx); err != nil {
return nil, err
@ -86,32 +85,10 @@ func (atq *AuthTokensQuery) QueryUser() *UserQuery {
return query
}
// QueryRoles chains the current query on the "roles" edge.
func (atq *AuthTokensQuery) QueryRoles() *AuthRolesQuery {
query := (&AuthRolesClient{config: atq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := atq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := atq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(authtokens.Table, authtokens.FieldID, selector),
sqlgraph.To(authroles.Table, authroles.FieldID),
sqlgraph.Edge(sqlgraph.O2O, false, authtokens.RolesTable, authtokens.RolesColumn),
)
fromU = sqlgraph.SetNeighbors(atq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first AuthTokens entity from the query.
// Returns a *NotFoundError when no AuthTokens was found.
func (atq *AuthTokensQuery) First(ctx context.Context) (*AuthTokens, error) {
nodes, err := atq.Limit(1).All(setContextOp(ctx, atq.ctx, "First"))
nodes, err := atq.Limit(1).All(ctx)
if err != nil {
return nil, err
}
@ -134,7 +111,7 @@ func (atq *AuthTokensQuery) FirstX(ctx context.Context) *AuthTokens {
// Returns a *NotFoundError when no AuthTokens ID was found.
func (atq *AuthTokensQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = atq.Limit(1).IDs(setContextOp(ctx, atq.ctx, "FirstID")); err != nil {
if ids, err = atq.Limit(1).IDs(ctx); err != nil {
return
}
if len(ids) == 0 {
@ -157,7 +134,7 @@ func (atq *AuthTokensQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one AuthTokens entity is found.
// Returns a *NotFoundError when no AuthTokens entities are found.
func (atq *AuthTokensQuery) Only(ctx context.Context) (*AuthTokens, error) {
nodes, err := atq.Limit(2).All(setContextOp(ctx, atq.ctx, "Only"))
nodes, err := atq.Limit(2).All(ctx)
if err != nil {
return nil, err
}
@ -185,7 +162,7 @@ func (atq *AuthTokensQuery) OnlyX(ctx context.Context) *AuthTokens {
// Returns a *NotFoundError when no entities are found.
func (atq *AuthTokensQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = atq.Limit(2).IDs(setContextOp(ctx, atq.ctx, "OnlyID")); err != nil {
if ids, err = atq.Limit(2).IDs(ctx); err != nil {
return
}
switch len(ids) {
@ -210,12 +187,10 @@ func (atq *AuthTokensQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of AuthTokensSlice.
func (atq *AuthTokensQuery) All(ctx context.Context) ([]*AuthTokens, error) {
ctx = setContextOp(ctx, atq.ctx, "All")
if err := atq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*AuthTokens, *AuthTokensQuery]()
return withInterceptors[[]*AuthTokens](ctx, atq, qr, atq.inters)
return atq.sqlAll(ctx)
}
// AllX is like All, but panics if an error occurs.
@ -228,12 +203,9 @@ func (atq *AuthTokensQuery) AllX(ctx context.Context) []*AuthTokens {
}
// IDs executes the query and returns a list of AuthTokens IDs.
func (atq *AuthTokensQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if atq.ctx.Unique == nil && atq.path != nil {
atq.Unique(true)
}
ctx = setContextOp(ctx, atq.ctx, "IDs")
if err = atq.Select(authtokens.FieldID).Scan(ctx, &ids); err != nil {
func (atq *AuthTokensQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
if err := atq.Select(authtokens.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@ -250,11 +222,10 @@ func (atq *AuthTokensQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (atq *AuthTokensQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, atq.ctx, "Count")
if err := atq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, atq, querierCount[*AuthTokensQuery](), atq.inters)
return atq.sqlCount(ctx)
}
// CountX is like Count, but panics if an error occurs.
@ -268,15 +239,10 @@ func (atq *AuthTokensQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (atq *AuthTokensQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, atq.ctx, "Exist")
switch _, err := atq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
if err := atq.prepareQuery(ctx); err != nil {
return false, err
}
return atq.sqlExist(ctx)
}
// ExistX is like Exist, but panics if an error occurs.
@ -296,22 +262,22 @@ func (atq *AuthTokensQuery) Clone() *AuthTokensQuery {
}
return &AuthTokensQuery{
config: atq.config,
ctx: atq.ctx.Clone(),
order: append([]authtokens.OrderOption{}, atq.order...),
inters: append([]Interceptor{}, atq.inters...),
limit: atq.limit,
offset: atq.offset,
order: append([]OrderFunc{}, atq.order...),
predicates: append([]predicate.AuthTokens{}, atq.predicates...),
withUser: atq.withUser.Clone(),
withRoles: atq.withRoles.Clone(),
// clone intermediate query.
sql: atq.sql.Clone(),
path: atq.path,
unique: atq.unique,
}
}
// WithUser tells the query-builder to eager-load the nodes that are connected to
// the "user" edge. The optional arguments are used to configure the query builder of the edge.
func (atq *AuthTokensQuery) WithUser(opts ...func(*UserQuery)) *AuthTokensQuery {
query := (&UserClient{config: atq.config}).Query()
query := &UserQuery{config: atq.config}
for _, opt := range opts {
opt(query)
}
@ -319,17 +285,6 @@ func (atq *AuthTokensQuery) WithUser(opts ...func(*UserQuery)) *AuthTokensQuery
return atq
}
// WithRoles tells the query-builder to eager-load the nodes that are connected to
// the "roles" edge. The optional arguments are used to configure the query builder of the edge.
func (atq *AuthTokensQuery) WithRoles(opts ...func(*AuthRolesQuery)) *AuthTokensQuery {
query := (&AuthRolesClient{config: atq.config}).Query()
for _, opt := range opts {
opt(query)
}
atq.withRoles = query
return atq
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
@ -345,11 +300,16 @@ func (atq *AuthTokensQuery) WithRoles(opts ...func(*AuthRolesQuery)) *AuthTokens
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (atq *AuthTokensQuery) GroupBy(field string, fields ...string) *AuthTokensGroupBy {
atq.ctx.Fields = append([]string{field}, fields...)
grbuild := &AuthTokensGroupBy{build: atq}
grbuild.flds = &atq.ctx.Fields
grbuild := &AuthTokensGroupBy{config: atq.config}
grbuild.fields = append([]string{field}, fields...)
grbuild.path = func(ctx context.Context) (prev *sql.Selector, err error) {
if err := atq.prepareQuery(ctx); err != nil {
return nil, err
}
return atq.sqlQuery(ctx), nil
}
grbuild.label = authtokens.Label
grbuild.scan = grbuild.Scan
grbuild.flds, grbuild.scan = &grbuild.fields, grbuild.Scan
return grbuild
}
@ -366,30 +326,15 @@ func (atq *AuthTokensQuery) GroupBy(field string, fields ...string) *AuthTokensG
// Select(authtokens.FieldCreatedAt).
// Scan(ctx, &v)
func (atq *AuthTokensQuery) Select(fields ...string) *AuthTokensSelect {
atq.ctx.Fields = append(atq.ctx.Fields, fields...)
sbuild := &AuthTokensSelect{AuthTokensQuery: atq}
sbuild.label = authtokens.Label
sbuild.flds, sbuild.scan = &atq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AuthTokensSelect configured with the given aggregations.
func (atq *AuthTokensQuery) Aggregate(fns ...AggregateFunc) *AuthTokensSelect {
return atq.Select().Aggregate(fns...)
atq.fields = append(atq.fields, fields...)
selbuild := &AuthTokensSelect{AuthTokensQuery: atq}
selbuild.label = authtokens.Label
selbuild.flds, selbuild.scan = &atq.fields, selbuild.Scan
return selbuild
}
func (atq *AuthTokensQuery) prepareQuery(ctx context.Context) error {
for _, inter := range atq.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, atq); err != nil {
return err
}
}
}
for _, f := range atq.ctx.Fields {
for _, f := range atq.fields {
if !authtokens.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@ -409,9 +354,8 @@ func (atq *AuthTokensQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*
nodes = []*AuthTokens{}
withFKs = atq.withFKs
_spec = atq.querySpec()
loadedTypes = [2]bool{
loadedTypes = [1]bool{
atq.withUser != nil,
atq.withRoles != nil,
}
)
if atq.withUser != nil {
@ -444,12 +388,6 @@ func (atq *AuthTokensQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*
return nil, err
}
}
if query := atq.withRoles; query != nil {
if err := atq.loadRoles(ctx, query, nodes, nil,
func(n *AuthTokens, e *AuthRoles) { n.Edges.Roles = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
@ -466,9 +404,6 @@ func (atq *AuthTokensQuery) loadUser(ctx context.Context, query *UserQuery, node
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(user.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@ -485,53 +420,44 @@ func (atq *AuthTokensQuery) loadUser(ctx context.Context, query *UserQuery, node
}
return nil
}
func (atq *AuthTokensQuery) loadRoles(ctx context.Context, query *AuthRolesQuery, nodes []*AuthTokens, init func(*AuthTokens), assign func(*AuthTokens, *AuthRoles)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*AuthTokens)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
}
query.withFKs = true
query.Where(predicate.AuthRoles(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(authtokens.RolesColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.auth_tokens_roles
if fk == nil {
return fmt.Errorf(`foreign-key "auth_tokens_roles" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "auth_tokens_roles" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (atq *AuthTokensQuery) sqlCount(ctx context.Context) (int, error) {
_spec := atq.querySpec()
_spec.Node.Columns = atq.ctx.Fields
if len(atq.ctx.Fields) > 0 {
_spec.Unique = atq.ctx.Unique != nil && *atq.ctx.Unique
_spec.Node.Columns = atq.fields
if len(atq.fields) > 0 {
_spec.Unique = atq.unique != nil && *atq.unique
}
return sqlgraph.CountNodes(ctx, atq.driver, _spec)
}
func (atq *AuthTokensQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(authtokens.Table, authtokens.Columns, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
_spec.From = atq.sql
if unique := atq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if atq.path != nil {
_spec.Unique = true
func (atq *AuthTokensQuery) sqlExist(ctx context.Context) (bool, error) {
switch _, err := atq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
if fields := atq.ctx.Fields; len(fields) > 0 {
}
func (atq *AuthTokensQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
Columns: authtokens.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
From: atq.sql,
Unique: true,
}
if unique := atq.unique; unique != nil {
_spec.Unique = *unique
}
if fields := atq.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, authtokens.FieldID)
for i := range fields {
@ -547,10 +473,10 @@ func (atq *AuthTokensQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := atq.ctx.Limit; limit != nil {
if limit := atq.limit; limit != nil {
_spec.Limit = *limit
}
if offset := atq.ctx.Offset; offset != nil {
if offset := atq.offset; offset != nil {
_spec.Offset = *offset
}
if ps := atq.order; len(ps) > 0 {
@ -566,7 +492,7 @@ func (atq *AuthTokensQuery) querySpec() *sqlgraph.QuerySpec {
func (atq *AuthTokensQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(atq.driver.Dialect())
t1 := builder.Table(authtokens.Table)
columns := atq.ctx.Fields
columns := atq.fields
if len(columns) == 0 {
columns = authtokens.Columns
}
@ -575,7 +501,7 @@ func (atq *AuthTokensQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = atq.sql
selector.Select(selector.Columns(columns...)...)
}
if atq.ctx.Unique != nil && *atq.ctx.Unique {
if atq.unique != nil && *atq.unique {
selector.Distinct()
}
for _, p := range atq.predicates {
@ -584,12 +510,12 @@ func (atq *AuthTokensQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range atq.order {
p(selector)
}
if offset := atq.ctx.Offset; offset != nil {
if offset := atq.offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := atq.ctx.Limit; limit != nil {
if limit := atq.limit; limit != nil {
selector.Limit(*limit)
}
return selector
@ -597,8 +523,13 @@ func (atq *AuthTokensQuery) sqlQuery(ctx context.Context) *sql.Selector {
// AuthTokensGroupBy is the group-by builder for AuthTokens entities.
type AuthTokensGroupBy struct {
config
selector
build *AuthTokensQuery
fields []string
fns []AggregateFunc
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Aggregate adds the given aggregation functions to the group-by query.
@ -607,77 +538,74 @@ func (atgb *AuthTokensGroupBy) Aggregate(fns ...AggregateFunc) *AuthTokensGroupB
return atgb
}
// Scan applies the selector query and scans the result into the given value.
// Scan applies the group-by query and scans the result into the given value.
func (atgb *AuthTokensGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, atgb.build.ctx, "GroupBy")
if err := atgb.build.prepareQuery(ctx); err != nil {
query, err := atgb.path(ctx)
if err != nil {
return err
}
return scanWithInterceptors[*AuthTokensQuery, *AuthTokensGroupBy](ctx, atgb.build, atgb, atgb.build.inters, v)
atgb.sql = query
return atgb.sqlScan(ctx, v)
}
func (atgb *AuthTokensGroupBy) sqlScan(ctx context.Context, root *AuthTokensQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(atgb.fns))
for _, fn := range atgb.fns {
aggregation = append(aggregation, fn(selector))
func (atgb *AuthTokensGroupBy) sqlScan(ctx context.Context, v any) error {
for _, f := range atgb.fields {
if !authtokens.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)}
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*atgb.flds)+len(atgb.fns))
for _, f := range *atgb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*atgb.flds...)...)
selector := atgb.sqlQuery()
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := atgb.build.driver.Query(ctx, query, args, rows); err != nil {
if err := atgb.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
func (atgb *AuthTokensGroupBy) sqlQuery() *sql.Selector {
selector := atgb.sql.Select()
aggregation := make([]string, 0, len(atgb.fns))
for _, fn := range atgb.fns {
aggregation = append(aggregation, fn(selector))
}
// If no columns were selected in a custom aggregation function, the default
// selection is the fields used for "group-by", and the aggregation functions.
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(atgb.fields)+len(atgb.fns))
for _, f := range atgb.fields {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
return selector.GroupBy(selector.Columns(atgb.fields...)...)
}
// AuthTokensSelect is the builder for selecting fields of AuthTokens entities.
type AuthTokensSelect struct {
*AuthTokensQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (ats *AuthTokensSelect) Aggregate(fns ...AggregateFunc) *AuthTokensSelect {
ats.fns = append(ats.fns, fns...)
return ats
// intermediate query (i.e. traversal path).
sql *sql.Selector
}
// Scan applies the selector query and scans the result into the given value.
func (ats *AuthTokensSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, ats.ctx, "Select")
if err := ats.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*AuthTokensQuery, *AuthTokensSelect](ctx, ats.AuthTokensQuery, ats, ats.inters, v)
ats.sql = ats.AuthTokensQuery.sqlQuery(ctx)
return ats.sqlScan(ctx, v)
}
func (ats *AuthTokensSelect) sqlScan(ctx context.Context, root *AuthTokensQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(ats.fns))
for _, fn := range ats.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*ats.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
func (ats *AuthTokensSelect) sqlScan(ctx context.Context, v any) error {
rows := &sql.Rows{}
query, args := selector.Query()
query, args := ats.sql.Query()
if err := ats.driver.Query(ctx, query, args, rows); err != nil {
return err
}

View file

@ -12,10 +12,9 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/authroles"
"github.com/hay-kot/homebox/backend/internal/data/ent/authtokens"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
"github.com/hay-kot/homebox/backend/ent/authtokens"
"github.com/hay-kot/homebox/backend/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/user"
)
// AuthTokensUpdate is the builder for updating AuthTokens entities.
@ -76,25 +75,6 @@ func (atu *AuthTokensUpdate) SetUser(u *User) *AuthTokensUpdate {
return atu.SetUserID(u.ID)
}
// SetRolesID sets the "roles" edge to the AuthRoles entity by ID.
func (atu *AuthTokensUpdate) SetRolesID(id int) *AuthTokensUpdate {
atu.mutation.SetRolesID(id)
return atu
}
// SetNillableRolesID sets the "roles" edge to the AuthRoles entity by ID if the given value is not nil.
func (atu *AuthTokensUpdate) SetNillableRolesID(id *int) *AuthTokensUpdate {
if id != nil {
atu = atu.SetRolesID(*id)
}
return atu
}
// SetRoles sets the "roles" edge to the AuthRoles entity.
func (atu *AuthTokensUpdate) SetRoles(a *AuthRoles) *AuthTokensUpdate {
return atu.SetRolesID(a.ID)
}
// Mutation returns the AuthTokensMutation object of the builder.
func (atu *AuthTokensUpdate) Mutation() *AuthTokensMutation {
return atu.mutation
@ -106,16 +86,37 @@ func (atu *AuthTokensUpdate) ClearUser() *AuthTokensUpdate {
return atu
}
// ClearRoles clears the "roles" edge to the AuthRoles entity.
func (atu *AuthTokensUpdate) ClearRoles() *AuthTokensUpdate {
atu.mutation.ClearRoles()
return atu
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (atu *AuthTokensUpdate) Save(ctx context.Context) (int, error) {
var (
err error
affected int
)
atu.defaults()
return withHooks(ctx, atu.sqlSave, atu.mutation, atu.hooks)
if len(atu.hooks) == 0 {
affected, err = atu.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AuthTokensMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
atu.mutation = mutation
affected, err = atu.sqlSave(ctx)
mutation.done = true
return affected, err
})
for i := len(atu.hooks) - 1; i >= 0; i-- {
if atu.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = atu.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, atu.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// SaveX is like Save, but panics if an error occurs.
@ -149,7 +150,16 @@ func (atu *AuthTokensUpdate) defaults() {
}
func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
_spec := sqlgraph.NewUpdateSpec(authtokens.Table, authtokens.Columns, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
Columns: authtokens.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
}
if ps := atu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@ -158,13 +168,25 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
}
if value, ok := atu.mutation.UpdatedAt(); ok {
_spec.SetField(authtokens.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: authtokens.FieldUpdatedAt,
})
}
if value, ok := atu.mutation.Token(); ok {
_spec.SetField(authtokens.FieldToken, field.TypeBytes, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeBytes,
Value: value,
Column: authtokens.FieldToken,
})
}
if value, ok := atu.mutation.ExpiresAt(); ok {
_spec.SetField(authtokens.FieldExpiresAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: authtokens.FieldExpiresAt,
})
}
if atu.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
@ -174,7 +196,10 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.UserColumn},
Bidi: false,
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)
@ -187,36 +212,10 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if atu.mutation.RolesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2O,
Inverse: false,
Table: authtokens.RolesTable,
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := atu.mutation.RolesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2O,
Inverse: false,
Table: authtokens.RolesTable,
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
@ -232,7 +231,6 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
return 0, err
}
atu.mutation.done = true
return n, nil
}
@ -289,25 +287,6 @@ func (atuo *AuthTokensUpdateOne) SetUser(u *User) *AuthTokensUpdateOne {
return atuo.SetUserID(u.ID)
}
// SetRolesID sets the "roles" edge to the AuthRoles entity by ID.
func (atuo *AuthTokensUpdateOne) SetRolesID(id int) *AuthTokensUpdateOne {
atuo.mutation.SetRolesID(id)
return atuo
}
// SetNillableRolesID sets the "roles" edge to the AuthRoles entity by ID if the given value is not nil.
func (atuo *AuthTokensUpdateOne) SetNillableRolesID(id *int) *AuthTokensUpdateOne {
if id != nil {
atuo = atuo.SetRolesID(*id)
}
return atuo
}
// SetRoles sets the "roles" edge to the AuthRoles entity.
func (atuo *AuthTokensUpdateOne) SetRoles(a *AuthRoles) *AuthTokensUpdateOne {
return atuo.SetRolesID(a.ID)
}
// Mutation returns the AuthTokensMutation object of the builder.
func (atuo *AuthTokensUpdateOne) Mutation() *AuthTokensMutation {
return atuo.mutation
@ -319,18 +298,6 @@ func (atuo *AuthTokensUpdateOne) ClearUser() *AuthTokensUpdateOne {
return atuo
}
// ClearRoles clears the "roles" edge to the AuthRoles entity.
func (atuo *AuthTokensUpdateOne) ClearRoles() *AuthTokensUpdateOne {
atuo.mutation.ClearRoles()
return atuo
}
// Where appends a list predicates to the AuthTokensUpdate builder.
func (atuo *AuthTokensUpdateOne) Where(ps ...predicate.AuthTokens) *AuthTokensUpdateOne {
atuo.mutation.Where(ps...)
return atuo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (atuo *AuthTokensUpdateOne) Select(field string, fields ...string) *AuthTokensUpdateOne {
@ -340,8 +307,41 @@ func (atuo *AuthTokensUpdateOne) Select(field string, fields ...string) *AuthTok
// Save executes the query and returns the updated AuthTokens entity.
func (atuo *AuthTokensUpdateOne) Save(ctx context.Context) (*AuthTokens, error) {
var (
err error
node *AuthTokens
)
atuo.defaults()
return withHooks(ctx, atuo.sqlSave, atuo.mutation, atuo.hooks)
if len(atuo.hooks) == 0 {
node, err = atuo.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AuthTokensMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
atuo.mutation = mutation
node, err = atuo.sqlSave(ctx)
mutation.done = true
return node, err
})
for i := len(atuo.hooks) - 1; i >= 0; i-- {
if atuo.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = atuo.hooks[i](mut)
}
v, err := mut.Mutate(ctx, atuo.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*AuthTokens)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from AuthTokensMutation", v)
}
node = nv
}
return node, err
}
// SaveX is like Save, but panics if an error occurs.
@ -375,7 +375,16 @@ func (atuo *AuthTokensUpdateOne) defaults() {
}
func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens, err error) {
_spec := sqlgraph.NewUpdateSpec(authtokens.Table, authtokens.Columns, sqlgraph.NewFieldSpec(authtokens.FieldID, field.TypeUUID))
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: authtokens.Table,
Columns: authtokens.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: authtokens.FieldID,
},
},
}
id, ok := atuo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AuthTokens.id" for update`)}
@ -401,13 +410,25 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
}
}
if value, ok := atuo.mutation.UpdatedAt(); ok {
_spec.SetField(authtokens.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: authtokens.FieldUpdatedAt,
})
}
if value, ok := atuo.mutation.Token(); ok {
_spec.SetField(authtokens.FieldToken, field.TypeBytes, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeBytes,
Value: value,
Column: authtokens.FieldToken,
})
}
if value, ok := atuo.mutation.ExpiresAt(); ok {
_spec.SetField(authtokens.FieldExpiresAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: authtokens.FieldExpiresAt,
})
}
if atuo.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
@ -417,7 +438,10 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.UserColumn},
Bidi: false,
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)
@ -430,36 +454,10 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
Columns: []string{authtokens.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: user.FieldID,
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if atuo.mutation.RolesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2O,
Inverse: false,
Table: authtokens.RolesTable,
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := atuo.mutation.RolesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2O,
Inverse: false,
Table: authtokens.RolesTable,
Columns: []string{authtokens.RolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authroles.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
@ -478,6 +476,5 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens
}
return nil, err
}
atuo.mutation.done = true
return _node, nil
}

1601
backend/ent/client.go Normal file

File diff suppressed because it is too large Load diff

69
backend/ent/config.go Normal file
View file

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

33
backend/ent/context.go Normal file
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,11 +7,10 @@ import (
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/group"
)
// Document is the model entity for the Document schema.
@ -31,18 +30,19 @@ type Document struct {
// The values are being populated by the DocumentQuery when eager-loading is set.
Edges DocumentEdges `json:"edges"`
group_documents *uuid.UUID
selectValues sql.SelectValues
}
// DocumentEdges holds the relations/edges for other nodes in the graph.
type DocumentEdges struct {
// Group holds the value of the group edge.
Group *Group `json:"group,omitempty"`
// DocumentTokens holds the value of the document_tokens edge.
DocumentTokens []*DocumentToken `json:"document_tokens,omitempty"`
// Attachments holds the value of the attachments edge.
Attachments []*Attachment `json:"attachments,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [2]bool
loadedTypes [3]bool
}
// GroupOrErr returns the Group value or an error if the edge
@ -58,10 +58,19 @@ func (e DocumentEdges) GroupOrErr() (*Group, error) {
return nil, &NotLoadedError{edge: "group"}
}
// DocumentTokensOrErr returns the DocumentTokens value or an error if the edge
// was not loaded in eager-loading.
func (e DocumentEdges) DocumentTokensOrErr() ([]*DocumentToken, error) {
if e.loadedTypes[1] {
return e.DocumentTokens, nil
}
return nil, &NotLoadedError{edge: "document_tokens"}
}
// AttachmentsOrErr returns the Attachments value or an error if the edge
// was not loaded in eager-loading.
func (e DocumentEdges) AttachmentsOrErr() ([]*Attachment, error) {
if e.loadedTypes[1] {
if e.loadedTypes[2] {
return e.Attachments, nil
}
return nil, &NotLoadedError{edge: "attachments"}
@ -81,7 +90,7 @@ func (*Document) scanValues(columns []string) ([]any, error) {
case document.ForeignKeys[0]: // group_documents
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
default:
values[i] = new(sql.UnknownType)
return nil, fmt.Errorf("unexpected column %q for type Document", columns[i])
}
}
return values, nil
@ -132,34 +141,31 @@ func (d *Document) assignValues(columns []string, values []any) error {
d.group_documents = new(uuid.UUID)
*d.group_documents = *value.S.(*uuid.UUID)
}
default:
d.selectValues.Set(columns[i], values[i])
}
}
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.
func (d *Document) QueryGroup() *GroupQuery {
return NewDocumentClient(d.config).QueryGroup(d)
return (&DocumentClient{config: d.config}).QueryGroup(d)
}
// QueryDocumentTokens queries the "document_tokens" edge of the Document entity.
func (d *Document) QueryDocumentTokens() *DocumentTokenQuery {
return (&DocumentClient{config: d.config}).QueryDocumentTokens(d)
}
// QueryAttachments queries the "attachments" edge of the Document entity.
func (d *Document) QueryAttachments() *AttachmentQuery {
return NewDocumentClient(d.config).QueryAttachments(d)
return (&DocumentClient{config: d.config}).QueryAttachments(d)
}
// Update returns a builder for updating this Document.
// Note that you need to call Document.Unwrap() before calling this method if this Document
// was returned from a transaction, and the transaction was committed or rolled back.
func (d *Document) Update() *DocumentUpdateOne {
return NewDocumentClient(d.config).UpdateOne(d)
return (&DocumentClient{config: d.config}).UpdateOne(d)
}
// Unwrap unwraps the Document entity that was returned from a transaction after it was closed,
@ -195,3 +201,9 @@ func (d *Document) String() string {
// Documents is a parsable slice of Document.
type Documents []*Document
func (d Documents) config(cfg config) {
for _i := range d {
d[_i].config = cfg
}
}

View file

@ -5,8 +5,6 @@ package document
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
)
@ -25,6 +23,8 @@ const (
FieldPath = "path"
// EdgeGroup holds the string denoting the group edge name in mutations.
EdgeGroup = "group"
// EdgeDocumentTokens holds the string denoting the document_tokens edge name in mutations.
EdgeDocumentTokens = "document_tokens"
// EdgeAttachments holds the string denoting the attachments edge name in mutations.
EdgeAttachments = "attachments"
// Table holds the table name of the document in the database.
@ -36,6 +36,13 @@ const (
GroupInverseTable = "groups"
// GroupColumn is the table column denoting the group relation/edge.
GroupColumn = "group_documents"
// DocumentTokensTable is the table that holds the document_tokens relation/edge.
DocumentTokensTable = "document_tokens"
// DocumentTokensInverseTable is the table name for the DocumentToken entity.
// It exists in this package in order to avoid circular dependency with the "documenttoken" package.
DocumentTokensInverseTable = "document_tokens"
// DocumentTokensColumn is the table column denoting the document_tokens relation/edge.
DocumentTokensColumn = "document_document_tokens"
// AttachmentsTable is the table that holds the attachments relation/edge.
AttachmentsTable = "attachments"
// AttachmentsInverseTable is the table name for the Attachment entity.
@ -89,66 +96,3 @@ var (
// DefaultID holds the default value on creation for the "id" field.
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

@ -8,282 +8,432 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldID, id))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldID, id))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldID), id))
})
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldID, id))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldID), id))
})
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldIn(FieldID, ids...))
return predicate.Document(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.In(s.C(FieldID), v...))
})
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldID, ids...))
return predicate.Document(func(s *sql.Selector) {
v := make([]any, len(ids))
for i := range v {
v[i] = ids[i]
}
s.Where(sql.NotIn(s.C(FieldID), v...))
})
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldGT(FieldID, id))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldID), id))
})
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldID, id))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldID), id))
})
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldLT(FieldID, id))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldID), id))
})
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id uuid.UUID) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldID, id))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldID), id))
})
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldCreatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldUpdatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// Title applies equality check predicate on the "title" field. It's identical to TitleEQ.
func Title(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldTitle), v))
})
}
// Path applies equality check predicate on the "path" field. It's identical to PathEQ.
func Path(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldPath), v))
})
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldCreatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldCreatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldCreatedAt), v))
})
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldIn(FieldCreatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldCreatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldCreatedAt), v...))
})
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGT(FieldCreatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldCreatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLT(FieldCreatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldCreatedAt), v))
})
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldCreatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldCreatedAt), v))
})
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldUpdatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldUpdatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldIn(FieldUpdatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldUpdatedAt, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldUpdatedAt), v...))
})
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGT(FieldUpdatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldUpdatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLT(FieldUpdatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldUpdatedAt), v))
})
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldUpdatedAt, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldUpdatedAt), v))
})
}
// TitleEQ applies the EQ predicate on the "title" field.
func TitleEQ(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldTitle), v))
})
}
// TitleNEQ applies the NEQ predicate on the "title" field.
func TitleNEQ(v string) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldTitle), v))
})
}
// TitleIn applies the In predicate on the "title" field.
func TitleIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldIn(FieldTitle, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldTitle), v...))
})
}
// TitleNotIn applies the NotIn predicate on the "title" field.
func TitleNotIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldTitle, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldTitle), v...))
})
}
// TitleGT applies the GT predicate on the "title" field.
func TitleGT(v string) predicate.Document {
return predicate.Document(sql.FieldGT(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldTitle), v))
})
}
// TitleGTE applies the GTE predicate on the "title" field.
func TitleGTE(v string) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldTitle), v))
})
}
// TitleLT applies the LT predicate on the "title" field.
func TitleLT(v string) predicate.Document {
return predicate.Document(sql.FieldLT(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldTitle), v))
})
}
// TitleLTE applies the LTE predicate on the "title" field.
func TitleLTE(v string) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldTitle), v))
})
}
// TitleContains applies the Contains predicate on the "title" field.
func TitleContains(v string) predicate.Document {
return predicate.Document(sql.FieldContains(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.Contains(s.C(FieldTitle), v))
})
}
// TitleHasPrefix applies the HasPrefix predicate on the "title" field.
func TitleHasPrefix(v string) predicate.Document {
return predicate.Document(sql.FieldHasPrefix(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.HasPrefix(s.C(FieldTitle), v))
})
}
// TitleHasSuffix applies the HasSuffix predicate on the "title" field.
func TitleHasSuffix(v string) predicate.Document {
return predicate.Document(sql.FieldHasSuffix(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.HasSuffix(s.C(FieldTitle), v))
})
}
// TitleEqualFold applies the EqualFold predicate on the "title" field.
func TitleEqualFold(v string) predicate.Document {
return predicate.Document(sql.FieldEqualFold(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EqualFold(s.C(FieldTitle), v))
})
}
// TitleContainsFold applies the ContainsFold predicate on the "title" field.
func TitleContainsFold(v string) predicate.Document {
return predicate.Document(sql.FieldContainsFold(FieldTitle, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.ContainsFold(s.C(FieldTitle), v))
})
}
// PathEQ applies the EQ predicate on the "path" field.
func PathEQ(v string) predicate.Document {
return predicate.Document(sql.FieldEQ(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldPath), v))
})
}
// PathNEQ applies the NEQ predicate on the "path" field.
func PathNEQ(v string) predicate.Document {
return predicate.Document(sql.FieldNEQ(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldPath), v))
})
}
// PathIn applies the In predicate on the "path" field.
func PathIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldIn(FieldPath, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.In(s.C(FieldPath), v...))
})
}
// PathNotIn applies the NotIn predicate on the "path" field.
func PathNotIn(vs ...string) predicate.Document {
return predicate.Document(sql.FieldNotIn(FieldPath, vs...))
v := make([]any, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.NotIn(s.C(FieldPath), v...))
})
}
// PathGT applies the GT predicate on the "path" field.
func PathGT(v string) predicate.Document {
return predicate.Document(sql.FieldGT(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldPath), v))
})
}
// PathGTE applies the GTE predicate on the "path" field.
func PathGTE(v string) predicate.Document {
return predicate.Document(sql.FieldGTE(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldPath), v))
})
}
// PathLT applies the LT predicate on the "path" field.
func PathLT(v string) predicate.Document {
return predicate.Document(sql.FieldLT(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldPath), v))
})
}
// PathLTE applies the LTE predicate on the "path" field.
func PathLTE(v string) predicate.Document {
return predicate.Document(sql.FieldLTE(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldPath), v))
})
}
// PathContains applies the Contains predicate on the "path" field.
func PathContains(v string) predicate.Document {
return predicate.Document(sql.FieldContains(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.Contains(s.C(FieldPath), v))
})
}
// PathHasPrefix applies the HasPrefix predicate on the "path" field.
func PathHasPrefix(v string) predicate.Document {
return predicate.Document(sql.FieldHasPrefix(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.HasPrefix(s.C(FieldPath), v))
})
}
// PathHasSuffix applies the HasSuffix predicate on the "path" field.
func PathHasSuffix(v string) predicate.Document {
return predicate.Document(sql.FieldHasSuffix(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.HasSuffix(s.C(FieldPath), v))
})
}
// PathEqualFold applies the EqualFold predicate on the "path" field.
func PathEqualFold(v string) predicate.Document {
return predicate.Document(sql.FieldEqualFold(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.EqualFold(s.C(FieldPath), v))
})
}
// PathContainsFold applies the ContainsFold predicate on the "path" field.
func PathContainsFold(v string) predicate.Document {
return predicate.Document(sql.FieldContainsFold(FieldPath, v))
return predicate.Document(func(s *sql.Selector) {
s.Where(sql.ContainsFold(s.C(FieldPath), v))
})
}
// HasGroup applies the HasEdge predicate on the "group" edge.
@ -291,6 +441,7 @@ func HasGroup() predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(GroupTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
)
sqlgraph.HasNeighbors(s, step)
@ -300,7 +451,39 @@ func HasGroup() predicate.Document {
// HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates).
func HasGroupWith(preds ...predicate.Group) predicate.Document {
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) {
for _, p := range preds {
p(s)
}
})
})
}
// HasDocumentTokens applies the HasEdge predicate on the "document_tokens" edge.
func HasDocumentTokens() predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentTokensTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DocumentTokensTable, DocumentTokensColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasDocumentTokensWith applies the HasEdge predicate on the "document_tokens" edge with a given conditions (other predicates).
func HasDocumentTokensWith(preds ...predicate.DocumentToken) predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DocumentTokensInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DocumentTokensTable, DocumentTokensColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
@ -314,6 +497,7 @@ func HasAttachments() predicate.Document {
return predicate.Document(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AttachmentsTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, AttachmentsTable, AttachmentsColumn),
)
sqlgraph.HasNeighbors(s, step)
@ -323,7 +507,11 @@ func HasAttachments() predicate.Document {
// HasAttachmentsWith applies the HasEdge predicate on the "attachments" edge with a given conditions (other predicates).
func HasAttachmentsWith(preds ...predicate.Attachment) predicate.Document {
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) {
for _, p := range preds {
p(s)
@ -334,15 +522,32 @@ func HasAttachmentsWith(preds ...predicate.Attachment) predicate.Document {
// And groups predicates with the AND operator between them.
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.
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.
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

@ -11,9 +11,10 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group"
)
// DocumentCreate is the builder for creating a Document entity.
@ -88,6 +89,21 @@ func (dc *DocumentCreate) SetGroup(g *Group) *DocumentCreate {
return dc.SetGroupID(g.ID)
}
// AddDocumentTokenIDs adds the "document_tokens" edge to the DocumentToken entity by IDs.
func (dc *DocumentCreate) AddDocumentTokenIDs(ids ...uuid.UUID) *DocumentCreate {
dc.mutation.AddDocumentTokenIDs(ids...)
return dc
}
// AddDocumentTokens adds the "document_tokens" edges to the DocumentToken entity.
func (dc *DocumentCreate) AddDocumentTokens(d ...*DocumentToken) *DocumentCreate {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return dc.AddDocumentTokenIDs(ids...)
}
// AddAttachmentIDs adds the "attachments" edge to the Attachment entity by IDs.
func (dc *DocumentCreate) AddAttachmentIDs(ids ...uuid.UUID) *DocumentCreate {
dc.mutation.AddAttachmentIDs(ids...)
@ -110,8 +126,50 @@ func (dc *DocumentCreate) Mutation() *DocumentMutation {
// Save creates the Document in the database.
func (dc *DocumentCreate) Save(ctx context.Context) (*Document, error) {
var (
err error
node *Document
)
dc.defaults()
return withHooks(ctx, dc.sqlSave, dc.mutation, dc.hooks)
if len(dc.hooks) == 0 {
if err = dc.check(); err != nil {
return nil, err
}
node, err = dc.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = dc.check(); err != nil {
return nil, err
}
dc.mutation = mutation
if node, err = dc.sqlSave(ctx); err != nil {
return nil, err
}
mutation.id = &node.ID
mutation.done = true
return node, err
})
for i := len(dc.hooks) - 1; i >= 0; i-- {
if dc.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = dc.hooks[i](mut)
}
v, err := mut.Mutate(ctx, dc.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*Document)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from DocumentMutation", v)
}
node = nv
}
return node, err
}
// SaveX calls Save and panics if Save returns an error.
@ -183,9 +241,6 @@ func (dc *DocumentCreate) check() error {
}
func (dc *DocumentCreate) sqlSave(ctx context.Context) (*Document, error) {
if err := dc.check(); err != nil {
return nil, err
}
_node, _spec := dc.createSpec()
if err := sqlgraph.CreateNode(ctx, dc.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
@ -200,34 +255,54 @@ func (dc *DocumentCreate) sqlSave(ctx context.Context) (*Document, error) {
return nil, err
}
}
dc.mutation.id = &_node.ID
dc.mutation.done = true
return _node, nil
}
func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
var (
_node = &Document{config: dc.config}
_spec = sqlgraph.NewCreateSpec(document.Table, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
_spec = &sqlgraph.CreateSpec{
Table: document.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
}
)
if id, ok := dc.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = &id
}
if value, ok := dc.mutation.CreatedAt(); ok {
_spec.SetField(document.FieldCreatedAt, field.TypeTime, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: document.FieldCreatedAt,
})
_node.CreatedAt = value
}
if value, ok := dc.mutation.UpdatedAt(); ok {
_spec.SetField(document.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: document.FieldUpdatedAt,
})
_node.UpdatedAt = value
}
if value, ok := dc.mutation.Title(); ok {
_spec.SetField(document.FieldTitle, field.TypeString, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeString,
Value: value,
Column: document.FieldTitle,
})
_node.Title = value
}
if value, ok := dc.mutation.Path(); ok {
_spec.SetField(document.FieldPath, field.TypeString, value)
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeString,
Value: value,
Column: document.FieldPath,
})
_node.Path = value
}
if nodes := dc.mutation.GroupIDs(); len(nodes) > 0 {
@ -238,7 +313,10 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
},
}
for _, k := range nodes {
@ -247,6 +325,25 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
_node.group_documents = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := dc.mutation.DocumentTokensIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.DocumentTokensTable,
Columns: []string{document.DocumentTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := dc.mutation.AttachmentsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@ -255,7 +352,10 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
for _, k := range nodes {
@ -269,15 +369,11 @@ func (dc *DocumentCreate) createSpec() (*Document, *sqlgraph.CreateSpec) {
// DocumentCreateBulk is the builder for creating many Document entities in bulk.
type DocumentCreateBulk struct {
config
err error
builders []*DocumentCreate
}
// Save creates the Document entities in the database.
func (dcb *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) {
if dcb.err != nil {
return nil, dcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(dcb.builders))
nodes := make([]*Document, len(dcb.builders))
mutators := make([]Mutator, len(dcb.builders))
@ -294,8 +390,8 @@ func (dcb *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
var err error
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, dcb.builders[i+1].mutation)
} else {

View file

@ -4,12 +4,13 @@ package ent
import (
"context"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentDelete is the builder for deleting a Document entity.
@ -27,7 +28,34 @@ func (dd *DocumentDelete) Where(ps ...predicate.Document) *DocumentDelete {
// Exec executes the deletion query and returns how many vertices were deleted.
func (dd *DocumentDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, dd.sqlExec, dd.mutation, dd.hooks)
var (
err error
affected int
)
if len(dd.hooks) == 0 {
affected, err = dd.sqlExec(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
dd.mutation = mutation
affected, err = dd.sqlExec(ctx)
mutation.done = true
return affected, err
})
for i := len(dd.hooks) - 1; i >= 0; i-- {
if dd.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = dd.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, dd.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// ExecX is like Exec, but panics if an error occurs.
@ -40,7 +68,15 @@ func (dd *DocumentDelete) ExecX(ctx context.Context) int {
}
func (dd *DocumentDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(document.Table, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
_spec := &sqlgraph.DeleteSpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
if ps := dd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@ -52,7 +88,6 @@ func (dd *DocumentDelete) sqlExec(ctx context.Context) (int, error) {
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
dd.mutation.done = true
return affected, err
}
@ -61,12 +96,6 @@ type DocumentDeleteOne struct {
dd *DocumentDelete
}
// Where appends a list predicates to the DocumentDelete builder.
func (ddo *DocumentDeleteOne) Where(ps ...predicate.Document) *DocumentDeleteOne {
ddo.dd.mutation.Where(ps...)
return ddo
}
// Exec executes the deletion query.
func (ddo *DocumentDeleteOne) Exec(ctx context.Context) error {
n, err := ddo.dd.Exec(ctx)
@ -82,7 +111,5 @@ func (ddo *DocumentDeleteOne) Exec(ctx context.Context) error {
// ExecX is like Exec, but panics if an error occurs.
func (ddo *DocumentDeleteOne) ExecX(ctx context.Context) {
if err := ddo.Exec(ctx); err != nil {
panic(err)
}
ddo.dd.ExecX(ctx)
}

View file

@ -12,20 +12,24 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentQuery is the builder for querying Document entities.
type DocumentQuery struct {
config
ctx *QueryContext
order []document.OrderOption
inters []Interceptor
limit *int
offset *int
unique *bool
order []OrderFunc
fields []string
predicates []predicate.Document
withGroup *GroupQuery
withDocumentTokens *DocumentTokenQuery
withAttachments *AttachmentQuery
withFKs bool
// intermediate query (i.e. traversal path).
@ -39,34 +43,34 @@ func (dq *DocumentQuery) Where(ps ...predicate.Document) *DocumentQuery {
return dq
}
// Limit the number of records to be returned by this query.
// Limit adds a limit step to the query.
func (dq *DocumentQuery) Limit(limit int) *DocumentQuery {
dq.ctx.Limit = &limit
dq.limit = &limit
return dq
}
// Offset to start from.
// Offset adds an offset step to the query.
func (dq *DocumentQuery) Offset(offset int) *DocumentQuery {
dq.ctx.Offset = &offset
dq.offset = &offset
return dq
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (dq *DocumentQuery) Unique(unique bool) *DocumentQuery {
dq.ctx.Unique = &unique
dq.unique = &unique
return dq
}
// Order specifies how the records should be ordered.
func (dq *DocumentQuery) Order(o ...document.OrderOption) *DocumentQuery {
// Order adds an order step to the query.
func (dq *DocumentQuery) Order(o ...OrderFunc) *DocumentQuery {
dq.order = append(dq.order, o...)
return dq
}
// QueryGroup chains the current query on the "group" edge.
func (dq *DocumentQuery) QueryGroup() *GroupQuery {
query := (&GroupClient{config: dq.config}).Query()
query := &GroupQuery{config: dq.config}
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
@ -86,9 +90,31 @@ func (dq *DocumentQuery) QueryGroup() *GroupQuery {
return query
}
// QueryDocumentTokens chains the current query on the "document_tokens" edge.
func (dq *DocumentQuery) QueryDocumentTokens() *DocumentTokenQuery {
query := &DocumentTokenQuery{config: dq.config}
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := dq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(document.Table, document.FieldID, selector),
sqlgraph.To(documenttoken.Table, documenttoken.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, document.DocumentTokensTable, document.DocumentTokensColumn),
)
fromU = sqlgraph.SetNeighbors(dq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryAttachments chains the current query on the "attachments" edge.
func (dq *DocumentQuery) QueryAttachments() *AttachmentQuery {
query := (&AttachmentClient{config: dq.config}).Query()
query := &AttachmentQuery{config: dq.config}
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
@ -111,7 +137,7 @@ func (dq *DocumentQuery) QueryAttachments() *AttachmentQuery {
// First returns the first Document entity from the query.
// Returns a *NotFoundError when no Document was found.
func (dq *DocumentQuery) First(ctx context.Context) (*Document, error) {
nodes, err := dq.Limit(1).All(setContextOp(ctx, dq.ctx, "First"))
nodes, err := dq.Limit(1).All(ctx)
if err != nil {
return nil, err
}
@ -134,7 +160,7 @@ func (dq *DocumentQuery) FirstX(ctx context.Context) *Document {
// Returns a *NotFoundError when no Document ID was found.
func (dq *DocumentQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dq.Limit(1).IDs(setContextOp(ctx, dq.ctx, "FirstID")); err != nil {
if ids, err = dq.Limit(1).IDs(ctx); err != nil {
return
}
if len(ids) == 0 {
@ -157,7 +183,7 @@ func (dq *DocumentQuery) FirstIDX(ctx context.Context) uuid.UUID {
// Returns a *NotSingularError when more than one Document entity is found.
// Returns a *NotFoundError when no Document entities are found.
func (dq *DocumentQuery) Only(ctx context.Context) (*Document, error) {
nodes, err := dq.Limit(2).All(setContextOp(ctx, dq.ctx, "Only"))
nodes, err := dq.Limit(2).All(ctx)
if err != nil {
return nil, err
}
@ -185,7 +211,7 @@ func (dq *DocumentQuery) OnlyX(ctx context.Context) *Document {
// Returns a *NotFoundError when no entities are found.
func (dq *DocumentQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) {
var ids []uuid.UUID
if ids, err = dq.Limit(2).IDs(setContextOp(ctx, dq.ctx, "OnlyID")); err != nil {
if ids, err = dq.Limit(2).IDs(ctx); err != nil {
return
}
switch len(ids) {
@ -210,12 +236,10 @@ func (dq *DocumentQuery) OnlyIDX(ctx context.Context) uuid.UUID {
// All executes the query and returns a list of Documents.
func (dq *DocumentQuery) All(ctx context.Context) ([]*Document, error) {
ctx = setContextOp(ctx, dq.ctx, "All")
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Document, *DocumentQuery]()
return withInterceptors[[]*Document](ctx, dq, qr, dq.inters)
return dq.sqlAll(ctx)
}
// AllX is like All, but panics if an error occurs.
@ -228,12 +252,9 @@ func (dq *DocumentQuery) AllX(ctx context.Context) []*Document {
}
// IDs executes the query and returns a list of Document IDs.
func (dq *DocumentQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) {
if dq.ctx.Unique == nil && dq.path != nil {
dq.Unique(true)
}
ctx = setContextOp(ctx, dq.ctx, "IDs")
if err = dq.Select(document.FieldID).Scan(ctx, &ids); err != nil {
func (dq *DocumentQuery) IDs(ctx context.Context) ([]uuid.UUID, error) {
var ids []uuid.UUID
if err := dq.Select(document.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
@ -250,11 +271,10 @@ func (dq *DocumentQuery) IDsX(ctx context.Context) []uuid.UUID {
// Count returns the count of the given query.
func (dq *DocumentQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, dq.ctx, "Count")
if err := dq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, dq, querierCount[*DocumentQuery](), dq.inters)
return dq.sqlCount(ctx)
}
// CountX is like Count, but panics if an error occurs.
@ -268,15 +288,10 @@ func (dq *DocumentQuery) CountX(ctx context.Context) int {
// Exist returns true if the query has elements in the graph.
func (dq *DocumentQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, dq.ctx, "Exist")
switch _, err := dq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
if err := dq.prepareQuery(ctx); err != nil {
return false, err
}
return dq.sqlExist(ctx)
}
// ExistX is like Exist, but panics if an error occurs.
@ -296,22 +311,24 @@ func (dq *DocumentQuery) Clone() *DocumentQuery {
}
return &DocumentQuery{
config: dq.config,
ctx: dq.ctx.Clone(),
order: append([]document.OrderOption{}, dq.order...),
inters: append([]Interceptor{}, dq.inters...),
limit: dq.limit,
offset: dq.offset,
order: append([]OrderFunc{}, dq.order...),
predicates: append([]predicate.Document{}, dq.predicates...),
withGroup: dq.withGroup.Clone(),
withDocumentTokens: dq.withDocumentTokens.Clone(),
withAttachments: dq.withAttachments.Clone(),
// clone intermediate query.
sql: dq.sql.Clone(),
path: dq.path,
unique: dq.unique,
}
}
// WithGroup tells the query-builder to eager-load the nodes that are connected to
// the "group" edge. The optional arguments are used to configure the query builder of the edge.
func (dq *DocumentQuery) WithGroup(opts ...func(*GroupQuery)) *DocumentQuery {
query := (&GroupClient{config: dq.config}).Query()
query := &GroupQuery{config: dq.config}
for _, opt := range opts {
opt(query)
}
@ -319,10 +336,21 @@ func (dq *DocumentQuery) WithGroup(opts ...func(*GroupQuery)) *DocumentQuery {
return dq
}
// WithDocumentTokens tells the query-builder to eager-load the nodes that are connected to
// the "document_tokens" edge. The optional arguments are used to configure the query builder of the edge.
func (dq *DocumentQuery) WithDocumentTokens(opts ...func(*DocumentTokenQuery)) *DocumentQuery {
query := &DocumentTokenQuery{config: dq.config}
for _, opt := range opts {
opt(query)
}
dq.withDocumentTokens = query
return dq
}
// WithAttachments tells the query-builder to eager-load the nodes that are connected to
// the "attachments" edge. The optional arguments are used to configure the query builder of the edge.
func (dq *DocumentQuery) WithAttachments(opts ...func(*AttachmentQuery)) *DocumentQuery {
query := (&AttachmentClient{config: dq.config}).Query()
query := &AttachmentQuery{config: dq.config}
for _, opt := range opts {
opt(query)
}
@ -345,11 +373,16 @@ func (dq *DocumentQuery) WithAttachments(opts ...func(*AttachmentQuery)) *Docume
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (dq *DocumentQuery) GroupBy(field string, fields ...string) *DocumentGroupBy {
dq.ctx.Fields = append([]string{field}, fields...)
grbuild := &DocumentGroupBy{build: dq}
grbuild.flds = &dq.ctx.Fields
grbuild := &DocumentGroupBy{config: dq.config}
grbuild.fields = append([]string{field}, fields...)
grbuild.path = func(ctx context.Context) (prev *sql.Selector, err error) {
if err := dq.prepareQuery(ctx); err != nil {
return nil, err
}
return dq.sqlQuery(ctx), nil
}
grbuild.label = document.Label
grbuild.scan = grbuild.Scan
grbuild.flds, grbuild.scan = &grbuild.fields, grbuild.Scan
return grbuild
}
@ -366,30 +399,15 @@ func (dq *DocumentQuery) GroupBy(field string, fields ...string) *DocumentGroupB
// Select(document.FieldCreatedAt).
// Scan(ctx, &v)
func (dq *DocumentQuery) Select(fields ...string) *DocumentSelect {
dq.ctx.Fields = append(dq.ctx.Fields, fields...)
sbuild := &DocumentSelect{DocumentQuery: dq}
sbuild.label = document.Label
sbuild.flds, sbuild.scan = &dq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a DocumentSelect configured with the given aggregations.
func (dq *DocumentQuery) Aggregate(fns ...AggregateFunc) *DocumentSelect {
return dq.Select().Aggregate(fns...)
dq.fields = append(dq.fields, fields...)
selbuild := &DocumentSelect{DocumentQuery: dq}
selbuild.label = document.Label
selbuild.flds, selbuild.scan = &dq.fields, selbuild.Scan
return selbuild
}
func (dq *DocumentQuery) prepareQuery(ctx context.Context) error {
for _, inter := range dq.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, dq); err != nil {
return err
}
}
}
for _, f := range dq.ctx.Fields {
for _, f := range dq.fields {
if !document.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
@ -409,8 +427,9 @@ func (dq *DocumentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Doc
nodes = []*Document{}
withFKs = dq.withFKs
_spec = dq.querySpec()
loadedTypes = [2]bool{
loadedTypes = [3]bool{
dq.withGroup != nil,
dq.withDocumentTokens != nil,
dq.withAttachments != nil,
}
)
@ -444,6 +463,13 @@ func (dq *DocumentQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Doc
return nil, err
}
}
if query := dq.withDocumentTokens; query != nil {
if err := dq.loadDocumentTokens(ctx, query, nodes,
func(n *Document) { n.Edges.DocumentTokens = []*DocumentToken{} },
func(n *Document, e *DocumentToken) { n.Edges.DocumentTokens = append(n.Edges.DocumentTokens, e) }); err != nil {
return nil, err
}
}
if query := dq.withAttachments; query != nil {
if err := dq.loadAttachments(ctx, query, nodes,
func(n *Document) { n.Edges.Attachments = []*Attachment{} },
@ -467,9 +493,6 @@ func (dq *DocumentQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(group.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
@ -486,6 +509,37 @@ func (dq *DocumentQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes
}
return nil
}
func (dq *DocumentQuery) loadDocumentTokens(ctx context.Context, query *DocumentTokenQuery, nodes []*Document, init func(*Document), assign func(*Document, *DocumentToken)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*Document)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
query.withFKs = true
query.Where(predicate.DocumentToken(func(s *sql.Selector) {
s.Where(sql.InValues(document.DocumentTokensColumn, fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.document_document_tokens
if fk == nil {
return fmt.Errorf(`foreign-key "document_document_tokens" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected foreign-key "document_document_tokens" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQuery, nodes []*Document, init func(*Document), assign func(*Document, *Attachment)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[uuid.UUID]*Document)
@ -498,7 +552,7 @@ func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQ
}
query.withFKs = true
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)
if err != nil {
@ -511,7 +565,7 @@ func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQ
}
node, ok := nodeids[*fk]
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)
}
@ -520,22 +574,41 @@ func (dq *DocumentQuery) loadAttachments(ctx context.Context, query *AttachmentQ
func (dq *DocumentQuery) sqlCount(ctx context.Context) (int, error) {
_spec := dq.querySpec()
_spec.Node.Columns = dq.ctx.Fields
if len(dq.ctx.Fields) > 0 {
_spec.Unique = dq.ctx.Unique != nil && *dq.ctx.Unique
_spec.Node.Columns = dq.fields
if len(dq.fields) > 0 {
_spec.Unique = dq.unique != nil && *dq.unique
}
return sqlgraph.CountNodes(ctx, dq.driver, _spec)
}
func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
_spec.From = dq.sql
if unique := dq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if dq.path != nil {
_spec.Unique = true
func (dq *DocumentQuery) sqlExist(ctx context.Context) (bool, error) {
switch _, err := dq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
if fields := dq.ctx.Fields; len(fields) > 0 {
}
func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
_spec := &sqlgraph.QuerySpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
Columns: document.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
From: dq.sql,
Unique: true,
}
if unique := dq.unique; unique != nil {
_spec.Unique = *unique
}
if fields := dq.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, document.FieldID)
for i := range fields {
@ -551,10 +624,10 @@ func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
}
}
}
if limit := dq.ctx.Limit; limit != nil {
if limit := dq.limit; limit != nil {
_spec.Limit = *limit
}
if offset := dq.ctx.Offset; offset != nil {
if offset := dq.offset; offset != nil {
_spec.Offset = *offset
}
if ps := dq.order; len(ps) > 0 {
@ -570,7 +643,7 @@ func (dq *DocumentQuery) querySpec() *sqlgraph.QuerySpec {
func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(dq.driver.Dialect())
t1 := builder.Table(document.Table)
columns := dq.ctx.Fields
columns := dq.fields
if len(columns) == 0 {
columns = document.Columns
}
@ -579,7 +652,7 @@ func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
selector = dq.sql
selector.Select(selector.Columns(columns...)...)
}
if dq.ctx.Unique != nil && *dq.ctx.Unique {
if dq.unique != nil && *dq.unique {
selector.Distinct()
}
for _, p := range dq.predicates {
@ -588,12 +661,12 @@ func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
for _, p := range dq.order {
p(selector)
}
if offset := dq.ctx.Offset; offset != nil {
if offset := dq.offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := dq.ctx.Limit; limit != nil {
if limit := dq.limit; limit != nil {
selector.Limit(*limit)
}
return selector
@ -601,8 +674,13 @@ func (dq *DocumentQuery) sqlQuery(ctx context.Context) *sql.Selector {
// DocumentGroupBy is the group-by builder for Document entities.
type DocumentGroupBy struct {
config
selector
build *DocumentQuery
fields []string
fns []AggregateFunc
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Aggregate adds the given aggregation functions to the group-by query.
@ -611,77 +689,74 @@ func (dgb *DocumentGroupBy) Aggregate(fns ...AggregateFunc) *DocumentGroupBy {
return dgb
}
// Scan applies the selector query and scans the result into the given value.
// Scan applies the group-by query and scans the result into the given value.
func (dgb *DocumentGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, dgb.build.ctx, "GroupBy")
if err := dgb.build.prepareQuery(ctx); err != nil {
query, err := dgb.path(ctx)
if err != nil {
return err
}
return scanWithInterceptors[*DocumentQuery, *DocumentGroupBy](ctx, dgb.build, dgb, dgb.build.inters, v)
dgb.sql = query
return dgb.sqlScan(ctx, v)
}
func (dgb *DocumentGroupBy) sqlScan(ctx context.Context, root *DocumentQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(dgb.fns))
for _, fn := range dgb.fns {
aggregation = append(aggregation, fn(selector))
func (dgb *DocumentGroupBy) sqlScan(ctx context.Context, v any) error {
for _, f := range dgb.fields {
if !document.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)}
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*dgb.flds)+len(dgb.fns))
for _, f := range *dgb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*dgb.flds...)...)
selector := dgb.sqlQuery()
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := dgb.build.driver.Query(ctx, query, args, rows); err != nil {
if err := dgb.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
func (dgb *DocumentGroupBy) sqlQuery() *sql.Selector {
selector := dgb.sql.Select()
aggregation := make([]string, 0, len(dgb.fns))
for _, fn := range dgb.fns {
aggregation = append(aggregation, fn(selector))
}
// If no columns were selected in a custom aggregation function, the default
// selection is the fields used for "group-by", and the aggregation functions.
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(dgb.fields)+len(dgb.fns))
for _, f := range dgb.fields {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
return selector.GroupBy(selector.Columns(dgb.fields...)...)
}
// DocumentSelect is the builder for selecting fields of Document entities.
type DocumentSelect struct {
*DocumentQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (ds *DocumentSelect) Aggregate(fns ...AggregateFunc) *DocumentSelect {
ds.fns = append(ds.fns, fns...)
return ds
// intermediate query (i.e. traversal path).
sql *sql.Selector
}
// Scan applies the selector query and scans the result into the given value.
func (ds *DocumentSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, ds.ctx, "Select")
if err := ds.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*DocumentQuery, *DocumentSelect](ctx, ds.DocumentQuery, ds, ds.inters, v)
ds.sql = ds.DocumentQuery.sqlQuery(ctx)
return ds.sqlScan(ctx, v)
}
func (ds *DocumentSelect) sqlScan(ctx context.Context, root *DocumentQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(ds.fns))
for _, fn := range ds.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*ds.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
func (ds *DocumentSelect) sqlScan(ctx context.Context, v any) error {
rows := &sql.Rows{}
query, args := selector.Query()
query, args := ds.sql.Query()
if err := ds.driver.Query(ctx, query, args, rows); err != nil {
return err
}

View file

@ -12,10 +12,11 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/data/ent/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/group"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentUpdate is the builder for updating Document entities.
@ -43,28 +44,12 @@ func (du *DocumentUpdate) SetTitle(s string) *DocumentUpdate {
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.
func (du *DocumentUpdate) SetPath(s string) *DocumentUpdate {
du.mutation.SetPath(s)
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.
func (du *DocumentUpdate) SetGroupID(id uuid.UUID) *DocumentUpdate {
du.mutation.SetGroupID(id)
@ -76,6 +61,21 @@ func (du *DocumentUpdate) SetGroup(g *Group) *DocumentUpdate {
return du.SetGroupID(g.ID)
}
// AddDocumentTokenIDs adds the "document_tokens" edge to the DocumentToken entity by IDs.
func (du *DocumentUpdate) AddDocumentTokenIDs(ids ...uuid.UUID) *DocumentUpdate {
du.mutation.AddDocumentTokenIDs(ids...)
return du
}
// AddDocumentTokens adds the "document_tokens" edges to the DocumentToken entity.
func (du *DocumentUpdate) AddDocumentTokens(d ...*DocumentToken) *DocumentUpdate {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return du.AddDocumentTokenIDs(ids...)
}
// AddAttachmentIDs adds the "attachments" edge to the Attachment entity by IDs.
func (du *DocumentUpdate) AddAttachmentIDs(ids ...uuid.UUID) *DocumentUpdate {
du.mutation.AddAttachmentIDs(ids...)
@ -102,6 +102,27 @@ func (du *DocumentUpdate) ClearGroup() *DocumentUpdate {
return du
}
// ClearDocumentTokens clears all "document_tokens" edges to the DocumentToken entity.
func (du *DocumentUpdate) ClearDocumentTokens() *DocumentUpdate {
du.mutation.ClearDocumentTokens()
return du
}
// RemoveDocumentTokenIDs removes the "document_tokens" edge to DocumentToken entities by IDs.
func (du *DocumentUpdate) RemoveDocumentTokenIDs(ids ...uuid.UUID) *DocumentUpdate {
du.mutation.RemoveDocumentTokenIDs(ids...)
return du
}
// RemoveDocumentTokens removes "document_tokens" edges to DocumentToken entities.
func (du *DocumentUpdate) RemoveDocumentTokens(d ...*DocumentToken) *DocumentUpdate {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return du.RemoveDocumentTokenIDs(ids...)
}
// ClearAttachments clears all "attachments" edges to the Attachment entity.
func (du *DocumentUpdate) ClearAttachments() *DocumentUpdate {
du.mutation.ClearAttachments()
@ -125,8 +146,41 @@ func (du *DocumentUpdate) RemoveAttachments(a ...*Attachment) *DocumentUpdate {
// Save executes the query and returns the number of nodes affected by the update operation.
func (du *DocumentUpdate) Save(ctx context.Context) (int, error) {
var (
err error
affected int
)
du.defaults()
return withHooks(ctx, du.sqlSave, du.mutation, du.hooks)
if len(du.hooks) == 0 {
if err = du.check(); err != nil {
return 0, err
}
affected, err = du.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = du.check(); err != nil {
return 0, err
}
du.mutation = mutation
affected, err = du.sqlSave(ctx)
mutation.done = true
return affected, err
})
for i := len(du.hooks) - 1; i >= 0; i-- {
if du.hooks[i] == nil {
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = du.hooks[i](mut)
}
if _, err := mut.Mutate(ctx, du.mutation); err != nil {
return 0, err
}
}
return affected, err
}
// SaveX is like Save, but panics if an error occurs.
@ -178,10 +232,16 @@ func (du *DocumentUpdate) check() error {
}
func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := du.check(); err != nil {
return n, err
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
Columns: document.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
if ps := du.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
@ -190,13 +250,25 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
}
if value, ok := du.mutation.UpdatedAt(); ok {
_spec.SetField(document.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: document.FieldUpdatedAt,
})
}
if value, ok := du.mutation.Title(); ok {
_spec.SetField(document.FieldTitle, field.TypeString, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,
Value: value,
Column: document.FieldTitle,
})
}
if value, ok := du.mutation.Path(); ok {
_spec.SetField(document.FieldPath, field.TypeString, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,
Value: value,
Column: document.FieldPath,
})
}
if du.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
@ -206,7 +278,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.GroupColumn},
Bidi: false,
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)
@ -219,7 +294,64 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if du.mutation.DocumentTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.DocumentTokensTable,
Columns: []string{document.DocumentTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := du.mutation.RemovedDocumentTokensIDs(); len(nodes) > 0 && !du.mutation.DocumentTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.DocumentTokensTable,
Columns: []string{document.DocumentTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := du.mutation.DocumentTokensIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.DocumentTokensTable,
Columns: []string{document.DocumentTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
for _, k := range nodes {
@ -235,7 +367,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
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)
@ -248,7 +383,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
for _, k := range nodes {
@ -264,7 +402,10 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
for _, k := range nodes {
@ -280,7 +421,6 @@ func (du *DocumentUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
return 0, err
}
du.mutation.done = true
return n, nil
}
@ -304,28 +444,12 @@ func (duo *DocumentUpdateOne) SetTitle(s string) *DocumentUpdateOne {
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.
func (duo *DocumentUpdateOne) SetPath(s string) *DocumentUpdateOne {
duo.mutation.SetPath(s)
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.
func (duo *DocumentUpdateOne) SetGroupID(id uuid.UUID) *DocumentUpdateOne {
duo.mutation.SetGroupID(id)
@ -337,6 +461,21 @@ func (duo *DocumentUpdateOne) SetGroup(g *Group) *DocumentUpdateOne {
return duo.SetGroupID(g.ID)
}
// AddDocumentTokenIDs adds the "document_tokens" edge to the DocumentToken entity by IDs.
func (duo *DocumentUpdateOne) AddDocumentTokenIDs(ids ...uuid.UUID) *DocumentUpdateOne {
duo.mutation.AddDocumentTokenIDs(ids...)
return duo
}
// AddDocumentTokens adds the "document_tokens" edges to the DocumentToken entity.
func (duo *DocumentUpdateOne) AddDocumentTokens(d ...*DocumentToken) *DocumentUpdateOne {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return duo.AddDocumentTokenIDs(ids...)
}
// AddAttachmentIDs adds the "attachments" edge to the Attachment entity by IDs.
func (duo *DocumentUpdateOne) AddAttachmentIDs(ids ...uuid.UUID) *DocumentUpdateOne {
duo.mutation.AddAttachmentIDs(ids...)
@ -363,6 +502,27 @@ func (duo *DocumentUpdateOne) ClearGroup() *DocumentUpdateOne {
return duo
}
// ClearDocumentTokens clears all "document_tokens" edges to the DocumentToken entity.
func (duo *DocumentUpdateOne) ClearDocumentTokens() *DocumentUpdateOne {
duo.mutation.ClearDocumentTokens()
return duo
}
// RemoveDocumentTokenIDs removes the "document_tokens" edge to DocumentToken entities by IDs.
func (duo *DocumentUpdateOne) RemoveDocumentTokenIDs(ids ...uuid.UUID) *DocumentUpdateOne {
duo.mutation.RemoveDocumentTokenIDs(ids...)
return duo
}
// RemoveDocumentTokens removes "document_tokens" edges to DocumentToken entities.
func (duo *DocumentUpdateOne) RemoveDocumentTokens(d ...*DocumentToken) *DocumentUpdateOne {
ids := make([]uuid.UUID, len(d))
for i := range d {
ids[i] = d[i].ID
}
return duo.RemoveDocumentTokenIDs(ids...)
}
// ClearAttachments clears all "attachments" edges to the Attachment entity.
func (duo *DocumentUpdateOne) ClearAttachments() *DocumentUpdateOne {
duo.mutation.ClearAttachments()
@ -384,12 +544,6 @@ func (duo *DocumentUpdateOne) RemoveAttachments(a ...*Attachment) *DocumentUpdat
return duo.RemoveAttachmentIDs(ids...)
}
// Where appends a list predicates to the DocumentUpdate builder.
func (duo *DocumentUpdateOne) Where(ps ...predicate.Document) *DocumentUpdateOne {
duo.mutation.Where(ps...)
return duo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (duo *DocumentUpdateOne) Select(field string, fields ...string) *DocumentUpdateOne {
@ -399,8 +553,47 @@ func (duo *DocumentUpdateOne) Select(field string, fields ...string) *DocumentUp
// Save executes the query and returns the updated Document entity.
func (duo *DocumentUpdateOne) Save(ctx context.Context) (*Document, error) {
var (
err error
node *Document
)
duo.defaults()
return withHooks(ctx, duo.sqlSave, duo.mutation, duo.hooks)
if len(duo.hooks) == 0 {
if err = duo.check(); err != nil {
return nil, err
}
node, err = duo.sqlSave(ctx)
} else {
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*DocumentMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err = duo.check(); err != nil {
return nil, err
}
duo.mutation = mutation
node, err = duo.sqlSave(ctx)
mutation.done = true
return node, err
})
for i := len(duo.hooks) - 1; i >= 0; i-- {
if duo.hooks[i] == nil {
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = duo.hooks[i](mut)
}
v, err := mut.Mutate(ctx, duo.mutation)
if err != nil {
return nil, err
}
nv, ok := v.(*Document)
if !ok {
return nil, fmt.Errorf("unexpected node type %T returned from DocumentMutation", v)
}
node = nv
}
return node, err
}
// SaveX is like Save, but panics if an error occurs.
@ -452,10 +645,16 @@ func (duo *DocumentUpdateOne) check() error {
}
func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err error) {
if err := duo.check(); err != nil {
return _node, err
_spec := &sqlgraph.UpdateSpec{
Node: &sqlgraph.NodeSpec{
Table: document.Table,
Columns: document.Columns,
ID: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: document.FieldID,
},
},
}
_spec := sqlgraph.NewUpdateSpec(document.Table, document.Columns, sqlgraph.NewFieldSpec(document.FieldID, field.TypeUUID))
id, ok := duo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Document.id" for update`)}
@ -481,13 +680,25 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
}
}
if value, ok := duo.mutation.UpdatedAt(); ok {
_spec.SetField(document.FieldUpdatedAt, field.TypeTime, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: document.FieldUpdatedAt,
})
}
if value, ok := duo.mutation.Title(); ok {
_spec.SetField(document.FieldTitle, field.TypeString, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,
Value: value,
Column: document.FieldTitle,
})
}
if value, ok := duo.mutation.Path(); ok {
_spec.SetField(document.FieldPath, field.TypeString, value)
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,
Value: value,
Column: document.FieldPath,
})
}
if duo.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
@ -497,7 +708,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.GroupColumn},
Bidi: false,
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)
@ -510,7 +724,64 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: group.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if duo.mutation.DocumentTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.DocumentTokensTable,
Columns: []string{document.DocumentTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := duo.mutation.RemovedDocumentTokensIDs(); len(nodes) > 0 && !duo.mutation.DocumentTokensCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.DocumentTokensTable,
Columns: []string{document.DocumentTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := duo.mutation.DocumentTokensIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: document.DocumentTokensTable,
Columns: []string{document.DocumentTokensColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: documenttoken.FieldID,
},
},
}
for _, k := range nodes {
@ -526,7 +797,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn},
Bidi: false,
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)
@ -539,7 +813,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
for _, k := range nodes {
@ -555,7 +832,10 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
Columns: []string{document.AttachmentsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeUUID),
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: attachment.FieldID,
},
},
}
for _, k := range nodes {
@ -574,6 +854,5 @@ func (duo *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err
}
return nil, err
}
duo.mutation.done = true
return _node, nil
}

View file

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

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