Compare commits

..

1 Commits

Author SHA1 Message Date
Hayden
a89ac74888 move datepicker buttons to bottom 2022-10-12 13:28:47 -08:00
288 changed files with 3281 additions and 7883 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.19"
}
}

1
.github/FUNDING.yml vendored
View File

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

View File

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

View File

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

View File

@@ -16,8 +16,6 @@ jobs:
- name: Install Task
uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
@@ -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

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

View File

@@ -60,7 +60,6 @@ jobs:
--tag ghcr.io/hay-kot/homebox:nightly \
--tag ghcr.io/hay-kot/homebox:latest \
--tag ghcr.io/hay-kot/homebox:${{ inputs.tag }} \
--build-arg VERSION=${{ inputs.tag }} \
--build-arg COMMIT=$(git rev-parse HEAD) \
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--platform linux/amd64,linux/arm64,linux/arm/v7 .

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

2
.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/*

View File

@@ -10,8 +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"
]
}
}

View File

@@ -1,51 +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 API's, update the documentation.
4. Ensure that the test suite and linters pass
5. Issue your pull request
## How To Get Started
### Prerequisites
There is a devcontainer available for this project. If you are using VSCode, you can use the devcontainer to get started. If you are not using VSCode, you can need to ensure that you have the following tools installed:
- [Go 1.19+](https://golang.org/doc/install)
- [Swaggo](https://github.com/swaggo/swag)
- [Node.js 16+](https://nodejs.org/en/download/)
- [pnpm](https://pnpm.io/installation)
- [Taskfile](https://taskfile.dev/#/installation) (Optional but recommended)
- For code generation, you'll need to have `python3` available on your path. In most cases, this is already installed and available.
If you're using `taskfile` you can run `task --list-all` for a list of all commands and their descriptions.
### Setup
If you're using the taskfile you can use the `task setup` command to run the required setup commands. Otherwise you can review the commands required in the `Taskfile.yml` file.
Note that when installing dependencies with pnpm you must use the `--shamefully-hoist` flag. If you don't use this flag you will get an error when running the the frontend server.
### API Development Notes
start command `task go:run`
1. API Server does not auto reload. You'll need to restart the server after making changes.
2. Unit tests should be written in Go, however end-to-end or user story tests should be written in TypeScript using the client library in the frontend directory.
### Frontend Development Notes
start command `task: ui:dev`
1. The frontend is a Vue 3 app with Nuxt.js that uses Tailwind and DaisyUI for styling.
2. We're using Vitest for our automated testing. you can run these with `task ui:watch`.
3. Tests require the API server to be running and in some cases the first run will fail due to a race condition. If this happens just run the tests again and they should pass.

View File

@@ -12,7 +12,6 @@ RUN pnpm build
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++
@@ -21,9 +20,9 @@ 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
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 -X main.version=$VERSION" \
-ldflags "-s -w -X main.Commit=$COMMIT -X main.BuildTime=$BUILD_TIME" \
-o /go/bin/api \
-v ./app/api/*.go

View File

@@ -2,15 +2,8 @@ version: "3"
env:
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
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
tasks:
generate:
desc: |
Generates collateral files from the backend project
@@ -18,27 +11,26 @@ tasks:
deps:
- db:generate
cmds:
- cd backend/app/api/static && swag fmt --dir=../
- cd backend/app/api/static && swag init --dir=../,../../../internal,../../../pkgs
- 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
- python3 ./scripts/process-types.py ./frontend/lib/api/types/data-contracts.ts
sources:
- "./backend/app/api/**/*"
- "./backend/internal/data/**"
- "./backend/internal/repo/**/*"
- "./backend/internal/services/**/*"
- "./scripts/process-types.py"
generates:
- "./frontend/lib/api/types/data-contracts.ts"
- "./backend/internal/data/ent/schema"
- "./backend/app/api/static/docs/swagger.json"
- "./backend/app/api/static/docs/swagger.yaml"
- "./backend/app/api/docs/swagger.json"
- "./backend/app/api/docs/swagger.yaml"
go:run:
api:
desc: Starts the backend api server (depends on generate task)
deps:
- generate
@@ -46,72 +38,57 @@ tasks:
- cd backend && go run ./app/api/ {{ .CLI_ARGS }}
silent: false
go:test:
desc: Runs all go tests using gotestsum - supports passing gotestsum args
api:build:
cmds:
- cd backend && gotestsum {{ .CLI_ARGS }} ./...
- cd backend && go build ./app/api/
silent: true
go:coverage:
desc: Runs all go tests with -race flag and generates a coverage report
api:test:
cmds:
- cd backend && go test ./app/api/
silent: true
api:watch:
cmds:
- cd backend && gotestsum --watch ./...
api:coverage:
cmds:
- cd backend && go test -race -coverprofile=coverage.out -covermode=atomic ./app/... ./internal/... ./pkgs/... -v -cover
silent: true
go:tidy:
desc: Runs go mod tidy on the backend
cmds:
- cd backend && go mod tidy
go:lint:
desc: Runs golangci-lint
cmds:
- cd backend && golangci-lint run ./...
go:all:
desc: Runs all go test and lint related tasks
cmds:
- task: go:tidy
- task: go:lint
- task: go:test
go:build:
desc: Builds the backend binary
cmds:
- cd backend && go build -o ../build/backend ./app/api
db:generate:
desc: Run Entgo.io Code Generation
cmds:
- |
cd backend/internal/ && go generate ./... \
--template=./data/ent/schema/templates/has_id.tmpl
sources:
- "./backend/internal/data/ent/schema/**/*"
generates:
- "./backend/internal/ent/"
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
cmds:
- cd frontend && pnpm run test:watch
ui:dev:
desc: Run frontend development server
cmds:
- cd frontend && pnpm dev
test:ci:
desc: Runs end-to-end test on a live server (only for use in CI)
cmds:
- cd backend && go build ./app/api
- backend/api &
- sleep 5
- cd frontend && pnpm run test:ci
silent: true
frontend:watch:
desc: Starts the vitest test runner in watch mode
cmds:
- cd frontend && pnpm vitest --watch
frontend:
desc: Run frontend development server
cmds:
- cd frontend && pnpm dev
db:generate:
desc: Run Entgo.io Code Generation
cmds:
- |
cd backend && go generate ./... \
--template=ent/schema/templates/has_id.tmpl
sources:
- "./backend/ent/schema/**/*"
generates:
- "./backend/ent/"
db:migration:
desc: Runs the database diff engine to generate a SQL migration files
deps:
- db:generate
cmds:
- cd backend && go run app/migrations/main.go {{ .CLI_ARGS }}

View File

@@ -3,10 +3,10 @@ package main
import (
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/config"
"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"
)

View File

@@ -5,7 +5,7 @@ import (
"encoding/csv"
"strings"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/rs/zerolog/log"
)
@@ -21,7 +21,7 @@ func (a *app) SetupDemo() {
var (
registration = services.UserRegistration{
Email: "demo@example.com",
Email: "demo@email.com",
Name: "Demo",
Password: "demo",
}
@@ -52,7 +52,7 @@ func (a *app) SetupDemo() {
log.Fatal().Msg("Failed to setup demo")
}
_, err = a.services.Items.CsvImport(context.Background(), self.GroupID, records)
err = a.services.Items.CsvImport(context.Background(), self.GroupID, records)
if err != nil {
log.Err(err).Msg("Failed to import CSV")
log.Fatal().Msg("Failed to setup demo")

View File

@@ -21,63 +21,6 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/v1/groups": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Get the current user's group",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.Group"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Updates some fields of the current users group",
"parameters": [
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.GroupUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.Group"
}
}
}
}
},
"/v1/groups/invitations": {
"post": {
"security": [
@@ -89,7 +32,7 @@ const docTemplate = `{
"application/json"
],
"tags": [
"Group"
"User"
],
"summary": "Get the current user",
"parameters": [
@@ -113,30 +56,6 @@ const docTemplate = `{
}
}
},
"/v1/groups/statistics": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Get the current user's group",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.GroupStatistics"
}
}
}
}
},
"/v1/items": {
"get": {
"security": [
@@ -151,51 +70,26 @@ const docTemplate = `{
"Items"
],
"summary": "Get All Items",
"parameters": [
{
"type": "string",
"description": "search string",
"name": "q",
"in": "query"
},
{
"type": "integer",
"description": "page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "items per page",
"name": "pageSize",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "label Ids",
"name": "labels",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "location Ids",
"name": "locations",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.PaginationResult-repo_ItemSummary"
"allOf": [
{
"$ref": "#/definitions/server.Results"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
}
}
}
]
}
}
}
@@ -259,7 +153,7 @@ const docTemplate = `{
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -360,7 +254,7 @@ const docTemplate = `{
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -376,7 +270,7 @@ const docTemplate = `{
"application/json"
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "imports items into the database",
"parameters": [
@@ -419,7 +313,10 @@ const docTemplate = `{
"422": {
"description": "Unprocessable Entity",
"schema": {
"$ref": "#/definitions/server.ErrorResponse"
"type": "array",
"items": {
"$ref": "#/definitions/server.ValidationError"
}
}
}
}
@@ -436,7 +333,7 @@ const docTemplate = `{
"application/octet-stream"
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -457,7 +354,7 @@ const docTemplate = `{
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
}
}
@@ -473,7 +370,7 @@ const docTemplate = `{
"application/octet-stream"
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -508,7 +405,7 @@ const docTemplate = `{
}
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -552,7 +449,7 @@ const docTemplate = `{
}
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -573,7 +470,7 @@ const docTemplate = `{
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -737,7 +634,7 @@ const docTemplate = `{
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -756,14 +653,6 @@ const docTemplate = `{
"Locations"
],
"summary": "Get All Locations",
"parameters": [
{
"type": "boolean",
"description": "Filter locations with parents",
"name": "filterChildren",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
@@ -874,15 +763,6 @@ const docTemplate = `{
"name": "id",
"in": "path",
"required": true
},
{
"description": "Location Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.LocationUpdate"
}
}
],
"responses": {
@@ -918,7 +798,7 @@ const docTemplate = `{
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -966,7 +846,7 @@ const docTemplate = `{
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -1023,7 +903,7 @@ const docTemplate = `{
"summary": "User Logout",
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -1042,7 +922,7 @@ const docTemplate = `{
"summary": "User Token Refresh",
"responses": {
"200": {
"description": "OK"
"description": ""
}
}
}
@@ -1069,7 +949,7 @@ const docTemplate = `{
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -1169,7 +1049,28 @@ const docTemplate = `{
"summary": "Deletes the user account",
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
},
"/v1/users/self/password": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Update the current user's password // TODO:",
"responses": {
"204": {
"description": ""
}
}
}
@@ -1190,54 +1091,6 @@ const docTemplate = `{
}
}
},
"repo.Group": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"currency": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"repo.GroupStatistics": {
"type": "object",
"properties": {
"totalItems": {
"type": "integer"
},
"totalLabels": {
"type": "integer"
},
"totalLocations": {
"type": "integer"
},
"totalUsers": {
"type": "integer"
}
}
},
"repo.GroupUpdate": {
"type": "object",
"properties": {
"currency": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"repo.ItemAttachment": {
"type": "object",
"properties": {
@@ -1287,69 +1140,24 @@ const docTemplate = `{
},
"name": {
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
}
}
},
"repo.ItemField": {
"type": "object",
"properties": {
"booleanValue": {
"type": "boolean"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"numberValue": {
"type": "integer"
},
"textValue": {
"type": "string"
},
"timeValue": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"repo.ItemOut": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"attachments": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemAttachment"
}
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": {
"type": "string"
},
"description": {
"type": "string"
},
"fields": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemField"
}
},
"id": {
"type": "string"
},
@@ -1368,8 +1176,6 @@ const docTemplate = `{
},
"location": {
"description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
},
"manufacturer": {
@@ -1385,11 +1191,6 @@ const docTemplate = `{
"description": "Extras",
"type": "string"
},
"parent": {
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.ItemSummary"
},
"purchaseFrom": {
"type": "string"
},
@@ -1435,9 +1236,6 @@ const docTemplate = `{
"repo.ItemSummary": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"createdAt": {
"type": "string"
},
@@ -1458,8 +1256,6 @@ const docTemplate = `{
},
"location": {
"description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
},
"name": {
@@ -1476,18 +1272,9 @@ const docTemplate = `{
"repo.ItemUpdate": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"description": {
"type": "string"
},
"fields": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemField"
}
},
"id": {
"type": "string"
},
@@ -1521,11 +1308,6 @@ const docTemplate = `{
"description": "Extras",
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true,
"x-omitempty": true
},
"purchaseFrom": {
"type": "string"
},
@@ -1640,12 +1422,6 @@ const docTemplate = `{
"repo.LocationOut": {
"type": "object",
"properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LocationSummary"
}
},
"createdAt": {
"type": "string"
},
@@ -1664,9 +1440,6 @@ const docTemplate = `{
"name": {
"type": "string"
},
"parent": {
"$ref": "#/definitions/repo.LocationSummary"
},
"updatedAt": {
"type": "string"
}
@@ -1715,44 +1488,6 @@ const docTemplate = `{
}
}
},
"repo.LocationUpdate": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
}
}
},
"repo.PaginationResult-repo_ItemSummary": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"repo.UserOut": {
"type": "object",
"properties": {
@@ -1790,20 +1525,6 @@ const docTemplate = `{
}
}
},
"server.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"server.Result": {
"type": "object",
"properties": {
@@ -1820,7 +1541,20 @@ const docTemplate = `{
"server.Results": {
"type": "object",
"properties": {
"items": {}
"items": {
"type": "any"
}
}
},
"server.ValidationError": {
"type": "object",
"properties": {
"field": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"services.UserRegistration": {

View File

@@ -13,63 +13,6 @@
},
"basePath": "/api",
"paths": {
"/v1/groups": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Get the current user's group",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.Group"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Updates some fields of the current users group",
"parameters": [
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.GroupUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.Group"
}
}
}
}
},
"/v1/groups/invitations": {
"post": {
"security": [
@@ -81,7 +24,7 @@
"application/json"
],
"tags": [
"Group"
"User"
],
"summary": "Get the current user",
"parameters": [
@@ -105,30 +48,6 @@
}
}
},
"/v1/groups/statistics": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Group"
],
"summary": "Get the current user's group",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.GroupStatistics"
}
}
}
}
},
"/v1/items": {
"get": {
"security": [
@@ -143,51 +62,26 @@
"Items"
],
"summary": "Get All Items",
"parameters": [
{
"type": "string",
"description": "search string",
"name": "q",
"in": "query"
},
{
"type": "integer",
"description": "page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "items per page",
"name": "pageSize",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "label Ids",
"name": "labels",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "location Ids",
"name": "locations",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.PaginationResult-repo_ItemSummary"
"allOf": [
{
"$ref": "#/definitions/server.Results"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
}
}
}
]
}
}
}
@@ -251,7 +145,7 @@
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -352,7 +246,7 @@
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -368,7 +262,7 @@
"application/json"
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "imports items into the database",
"parameters": [
@@ -411,7 +305,10 @@
"422": {
"description": "Unprocessable Entity",
"schema": {
"$ref": "#/definitions/server.ErrorResponse"
"type": "array",
"items": {
"$ref": "#/definitions/server.ValidationError"
}
}
}
}
@@ -428,7 +325,7 @@
"application/octet-stream"
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -449,7 +346,7 @@
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
}
}
@@ -465,7 +362,7 @@
"application/octet-stream"
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -500,7 +397,7 @@
}
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -544,7 +441,7 @@
}
],
"tags": [
"Items Attachments"
"Items"
],
"summary": "retrieves an attachment for an item",
"parameters": [
@@ -565,7 +462,7 @@
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -729,7 +626,7 @@
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -748,14 +645,6 @@
"Locations"
],
"summary": "Get All Locations",
"parameters": [
{
"type": "boolean",
"description": "Filter locations with parents",
"name": "filterChildren",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
@@ -866,15 +755,6 @@
"name": "id",
"in": "path",
"required": true
},
{
"description": "Location Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.LocationUpdate"
}
}
],
"responses": {
@@ -910,7 +790,7 @@
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -958,7 +838,7 @@
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -1015,7 +895,7 @@
"summary": "User Logout",
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -1034,7 +914,7 @@
"summary": "User Token Refresh",
"responses": {
"200": {
"description": "OK"
"description": ""
}
}
}
@@ -1061,7 +941,7 @@
],
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
@@ -1161,7 +1041,28 @@
"summary": "Deletes the user account",
"responses": {
"204": {
"description": "No Content"
"description": ""
}
}
}
},
"/v1/users/self/password": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Update the current user's password // TODO:",
"responses": {
"204": {
"description": ""
}
}
}
@@ -1182,54 +1083,6 @@
}
}
},
"repo.Group": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"currency": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"repo.GroupStatistics": {
"type": "object",
"properties": {
"totalItems": {
"type": "integer"
},
"totalLabels": {
"type": "integer"
},
"totalLocations": {
"type": "integer"
},
"totalUsers": {
"type": "integer"
}
}
},
"repo.GroupUpdate": {
"type": "object",
"properties": {
"currency": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"repo.ItemAttachment": {
"type": "object",
"properties": {
@@ -1279,69 +1132,24 @@
},
"name": {
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
}
}
},
"repo.ItemField": {
"type": "object",
"properties": {
"booleanValue": {
"type": "boolean"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"numberValue": {
"type": "integer"
},
"textValue": {
"type": "string"
},
"timeValue": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"repo.ItemOut": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"attachments": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemAttachment"
}
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"createdAt": {
"type": "string"
},
"description": {
"type": "string"
},
"fields": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemField"
}
},
"id": {
"type": "string"
},
@@ -1360,8 +1168,6 @@
},
"location": {
"description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
},
"manufacturer": {
@@ -1377,11 +1183,6 @@
"description": "Extras",
"type": "string"
},
"parent": {
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.ItemSummary"
},
"purchaseFrom": {
"type": "string"
},
@@ -1427,9 +1228,6 @@
"repo.ItemSummary": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"createdAt": {
"type": "string"
},
@@ -1450,8 +1248,6 @@
},
"location": {
"description": "Edges",
"x-nullable": true,
"x-omitempty": true,
"$ref": "#/definitions/repo.LocationSummary"
},
"name": {
@@ -1468,18 +1264,9 @@
"repo.ItemUpdate": {
"type": "object",
"properties": {
"archived": {
"type": "boolean"
},
"description": {
"type": "string"
},
"fields": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemField"
}
},
"id": {
"type": "string"
},
@@ -1513,11 +1300,6 @@
"description": "Extras",
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true,
"x-omitempty": true
},
"purchaseFrom": {
"type": "string"
},
@@ -1632,12 +1414,6 @@
"repo.LocationOut": {
"type": "object",
"properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.LocationSummary"
}
},
"createdAt": {
"type": "string"
},
@@ -1656,9 +1432,6 @@
"name": {
"type": "string"
},
"parent": {
"$ref": "#/definitions/repo.LocationSummary"
},
"updatedAt": {
"type": "string"
}
@@ -1707,44 +1480,6 @@
}
}
},
"repo.LocationUpdate": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"parentId": {
"type": "string",
"x-nullable": true
}
}
},
"repo.PaginationResult-repo_ItemSummary": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.ItemSummary"
}
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"repo.UserOut": {
"type": "object",
"properties": {
@@ -1782,20 +1517,6 @@
}
}
},
"server.ErrorResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"fields": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"server.Result": {
"type": "object",
"properties": {
@@ -1812,7 +1533,20 @@
"server.Results": {
"type": "object",
"properties": {
"items": {}
"items": {
"type": "any"
}
}
},
"server.ValidationError": {
"type": "object",
"properties": {
"field": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"services.UserRegistration": {

View File

@@ -9,37 +9,6 @@ definitions:
title:
type: string
type: object
repo.Group:
properties:
createdAt:
type: string
currency:
type: string
id:
type: string
name:
type: string
updatedAt:
type: string
type: object
repo.GroupStatistics:
properties:
totalItems:
type: integer
totalLabels:
type: integer
totalLocations:
type: integer
totalUsers:
type: integer
type: object
repo.GroupUpdate:
properties:
currency:
type: string
name:
type: string
type: object
repo.ItemAttachment:
properties:
createdAt:
@@ -73,47 +42,17 @@ definitions:
type: string
name:
type: string
parentId:
type: string
x-nullable: true
type: object
repo.ItemField:
properties:
booleanValue:
type: boolean
id:
type: string
name:
type: string
numberValue:
type: integer
textValue:
type: string
timeValue:
type: string
type:
type: string
type: object
repo.ItemOut:
properties:
archived:
type: boolean
attachments:
items:
$ref: '#/definitions/repo.ItemAttachment'
type: array
children:
items:
$ref: '#/definitions/repo.ItemSummary'
type: array
createdAt:
type: string
description:
type: string
fields:
items:
$ref: '#/definitions/repo.ItemField'
type: array
id:
type: string
insured:
@@ -128,8 +67,6 @@ definitions:
location:
$ref: '#/definitions/repo.LocationSummary'
description: Edges
x-nullable: true
x-omitempty: true
manufacturer:
type: string
modelNumber:
@@ -139,10 +76,6 @@ definitions:
notes:
description: Extras
type: string
parent:
$ref: '#/definitions/repo.ItemSummary'
x-nullable: true
x-omitempty: true
purchaseFrom:
type: string
purchasePrice:
@@ -174,8 +107,6 @@ definitions:
type: object
repo.ItemSummary:
properties:
archived:
type: boolean
createdAt:
type: string
description:
@@ -191,8 +122,6 @@ definitions:
location:
$ref: '#/definitions/repo.LocationSummary'
description: Edges
x-nullable: true
x-omitempty: true
name:
type: string
quantity:
@@ -202,14 +131,8 @@ definitions:
type: object
repo.ItemUpdate:
properties:
archived:
type: boolean
description:
type: string
fields:
items:
$ref: '#/definitions/repo.ItemField'
type: array
id:
type: string
insured:
@@ -233,10 +156,6 @@ definitions:
notes:
description: Extras
type: string
parentId:
type: string
x-nullable: true
x-omitempty: true
purchaseFrom:
type: string
purchasePrice:
@@ -313,10 +232,6 @@ definitions:
type: object
repo.LocationOut:
properties:
children:
items:
$ref: '#/definitions/repo.LocationSummary'
type: array
createdAt:
type: string
description:
@@ -329,8 +244,6 @@ definitions:
type: array
name:
type: string
parent:
$ref: '#/definitions/repo.LocationSummary'
updatedAt:
type: string
type: object
@@ -362,31 +275,6 @@ definitions:
updatedAt:
type: string
type: object
repo.LocationUpdate:
properties:
description:
type: string
id:
type: string
name:
type: string
parentId:
type: string
x-nullable: true
type: object
repo.PaginationResult-repo_ItemSummary:
properties:
items:
items:
$ref: '#/definitions/repo.ItemSummary'
type: array
page:
type: integer
pageSize:
type: integer
total:
type: integer
type: object
repo.UserOut:
properties:
email:
@@ -411,15 +299,6 @@ definitions:
name:
type: string
type: object
server.ErrorResponse:
properties:
error:
type: string
fields:
additionalProperties:
type: string
type: object
type: object
server.Result:
properties:
details: {}
@@ -431,7 +310,15 @@ definitions:
type: object
server.Results:
properties:
items: {}
items:
type: any
type: object
server.ValidationError:
properties:
field:
type: string
reason:
type: string
type: object
services.UserRegistration:
properties:
@@ -516,40 +403,6 @@ info:
title: Go API Templates
version: "1.0"
paths:
/v1/groups:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.Group'
security:
- Bearer: []
summary: Get the current user's group
tags:
- Group
put:
parameters:
- description: User Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.GroupUpdate'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.Group'
security:
- Bearer: []
summary: Updates some fields of the current users group
tags:
- Group
/v1/groups/invitations:
post:
parameters:
@@ -570,57 +423,23 @@ paths:
- Bearer: []
summary: Get the current user
tags:
- Group
/v1/groups/statistics:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.GroupStatistics'
security:
- Bearer: []
summary: Get the current user's group
tags:
- Group
- User
/v1/items:
get:
parameters:
- description: search string
in: query
name: q
type: string
- description: page number
in: query
name: page
type: integer
- description: items per page
in: query
name: pageSize
type: integer
- collectionFormat: multi
description: label Ids
in: query
items:
type: string
name: labels
type: array
- collectionFormat: multi
description: location Ids
in: query
items:
type: string
name: locations
type: array
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.PaginationResult-repo_ItemSummary'
allOf:
- $ref: '#/definitions/server.Results'
- properties:
items:
items:
$ref: '#/definitions/repo.ItemSummary'
type: array
type: object
security:
- Bearer: []
summary: Get All Items
@@ -658,7 +477,7 @@ paths:
- application/json
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: deletes a item
@@ -741,12 +560,14 @@ paths:
"422":
description: Unprocessable Entity
schema:
$ref: '#/definitions/server.ErrorResponse'
items:
$ref: '#/definitions/server.ValidationError'
type: array
security:
- Bearer: []
summary: imports items into the database
tags:
- Items Attachments
- Items
/v1/items/{id}/attachments/{attachment_id}:
delete:
parameters:
@@ -762,12 +583,12 @@ paths:
type: string
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: retrieves an attachment for an item
tags:
- Items Attachments
- Items
get:
parameters:
- description: Item ID
@@ -791,7 +612,7 @@ paths:
- Bearer: []
summary: retrieves an attachment for an item
tags:
- Items Attachments
- Items
put:
parameters:
- description: Item ID
@@ -819,7 +640,7 @@ paths:
- Bearer: []
summary: retrieves an attachment for an item
tags:
- Items Attachments
- Items
/v1/items/{id}/attachments/download:
get:
parameters:
@@ -837,12 +658,12 @@ paths:
- application/octet-stream
responses:
"200":
description: OK
description: ""
security:
- Bearer: []
summary: retrieves an attachment for an item
tags:
- Items Attachments
- Items
/v1/items/import:
post:
parameters:
@@ -855,7 +676,7 @@ paths:
- application/json
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: imports items into the database
@@ -914,7 +735,7 @@ paths:
- application/json
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: deletes a label
@@ -960,11 +781,6 @@ paths:
- Labels
/v1/locations:
get:
parameters:
- description: Filter locations with parents
in: query
name: filterChildren
type: boolean
produces:
- application/json
responses:
@@ -1016,7 +832,7 @@ paths:
- application/json
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: deletes a location
@@ -1048,12 +864,6 @@ paths:
name: id
required: true
type: string
- description: Location Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.LocationUpdate'
produces:
- application/json
responses:
@@ -1089,7 +899,7 @@ paths:
$ref: '#/definitions/v1.ChangePassword'
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: Updates the users password
@@ -1125,7 +935,7 @@ paths:
post:
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: User Logout
@@ -1138,7 +948,7 @@ paths:
This does not validate that the user still exists within the database.
responses:
"200":
description: OK
description: ""
security:
- Bearer: []
summary: User Token Refresh
@@ -1157,7 +967,7 @@ paths:
- application/json
responses:
"204":
description: No Content
description: ""
summary: Get the current user
tags:
- User
@@ -1167,7 +977,7 @@ paths:
- application/json
responses:
"204":
description: No Content
description: ""
security:
- Bearer: []
summary: Deletes the user account
@@ -1216,6 +1026,18 @@ paths:
summary: Update the current user
tags:
- User
/v1/users/self/password:
put:
produces:
- application/json
responses:
"204":
description: ""
security:
- Bearer: []
summary: 'Update the current user''s password // TODO:'
tags:
- User
securityDefinitions:
Bearer:
description: '"Type ''Bearer TOKEN'' to correctly set the API Key"'

View File

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

View File

@@ -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.NewInvalidRouteKeyError(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,129 +0,0 @@
package v1
import (
"errors"
"net/http"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/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() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
loginForm := &LoginForm{}
switch r.Header.Get("Content-Type") {
case server.ContentFormUrlEncoded:
err := r.ParseForm()
if err != nil {
return server.Respond(w, http.StatusBadRequest, server.Wrap(err))
}
loginForm.Username = r.PostFormValue("username")
loginForm.Password = r.PostFormValue("password")
case server.ContentJSON:
err := server.Decode(r, loginForm)
if err != nil {
log.Err(err).Msg("failed to decode login form")
}
default:
return server.Respond(w, http.StatusBadRequest, errors.New("invalid content type"))
}
if loginForm.Username == "" || loginForm.Password == "" {
return validate.NewFieldErrors(
validate.FieldError{
Field: "username",
Error: "username or password is empty",
},
validate.FieldError{
Field: "password",
Error: "username or password is empty",
},
)
}
newToken, err := ctrl.svc.User.Login(r.Context(), loginForm.Username, loginForm.Password)
if err != nil {
return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError)
}
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() server.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)
}
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() server.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()
}
return server.Respond(w, http.StatusOK, newToken)
}
}

View File

@@ -1,137 +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/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"`
}
)
// HandleGroupGet godoc
// @Summary Get the current user's group
// @Tags Group
// @Produce json
// @Success 200 {object} repo.GroupStatistics
// @Router /v1/groups/statistics [Get]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
stats, err := ctrl.repo.Groups.GroupStatistics(ctx, ctx.GID)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, stats)
}
}
// HandleGroupGet godoc
// @Summary Get the current user's group
// @Tags Group
// @Produce json
// @Success 200 {object} repo.Group
// @Router /v1/groups [Get]
// @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() server.HandlerFunc {
return ctrl.handleGroupGeneral()
}
// HandleGroupUpdate godoc
// @Summary Updates some fields of the current users 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() server.HandlerFunc {
return ctrl.handleGroupGeneral()
}
func (ctrl *V1Controller) handleGroupGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
switch r.Method {
case http.MethodGet:
group, err := ctrl.repo.Groups.GroupByID(ctx, ctx.GID)
if err != nil {
log.Err(err).Msg("failed to get group")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, group)
case http.MethodPut:
data := repo.GroupUpdate{}
if err := server.Decode(r, &data); err != nil {
return validate.NewRequestError(err, http.StatusBadRequest)
}
group, err := ctrl.svc.Group.UpdateGroup(ctx, data)
if err != nil {
log.Err(err).Msg("failed to update group")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, group)
}
return nil
}
}
// HandleGroupInvitationsCreate godoc
// @Summary Get the current user
// @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() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
data := GroupInvitationCreate{}
if err := server.Decode(r, &data); err != nil {
log.Err(err).Msg("failed to decode user registration data")
return validate.NewRequestError(err, http.StatusBadRequest)
}
if data.ExpiresAt.IsZero() {
data.ExpiresAt = time.Now().Add(time.Hour * 24)
}
ctx := services.NewContext(r.Context())
token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt)
if err != nil {
log.Err(err).Msg("failed to create new token")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, GroupInvitation{
Token: token,
ExpiresAt: data.ExpiresAt,
Uses: data.Uses,
})
}
}

View File

@@ -1,198 +0,0 @@
package v1
import (
"encoding/csv"
"net/http"
"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/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleItemsGetAll godoc
// @Summary Get 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)
// @Success 200 {object} repo.PaginationResult[repo.ItemSummary]{}
// @Router /v1/items [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() server.HandlerFunc {
extractQuery := func(r *http.Request) repo.ItemQuery {
params := r.URL.Query()
return repo.ItemQuery{
Page: queryIntOrNegativeOne(params.Get("page")),
PageSize: queryIntOrNegativeOne(params.Get("perPage")),
Search: params.Get("q"),
LocationIDs: queryUUIDList(params, "locations"),
LabelIDs: queryUUIDList(params, "labels"),
IncludeArchived: queryBool(params.Get("includeArchived")),
}
}
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 {
log.Err(err).Msg("failed to get items")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, 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() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
createData := repo.ItemCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode request body")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
item, err := ctrl.repo.Items.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("failed to create item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, item)
}
}
// HandleItemGet godocs
// @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() server.HandlerFunc {
return ctrl.handleItemsGeneral()
}
// 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() server.HandlerFunc {
return ctrl.handleItemsGeneral()
}
// 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() server.HandlerFunc {
return ctrl.handleItemsGeneral()
}
func (ctrl *V1Controller) handleItemsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
items, err := ctrl.repo.Items.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to get item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, items)
case http.MethodDelete:
err = ctrl.repo.Items.DeleteByGroup(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
body := repo.ItemUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode request body")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.repo.Items.UpdateByGroup(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update item")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, result)
}
return nil
}
}
// 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() server.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)
}
reader := csv.NewReader(file)
data, err := reader.ReadAll()
if err != nil {
log.Err(err).Msg("failed to read csv")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
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")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
}
}

View File

@@ -1,232 +0,0 @@
package v1
import (
"errors"
"fmt"
"net/http"
"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/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 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} server.ErrorResponse
// @Router /v1/items/{id}/attachments [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() server.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.Respond(w, http.StatusUnprocessableEntity, errs)
}
attachmentType := r.FormValue("type")
if attachmentType == "" {
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.Respond(w, http.StatusCreated, item)
}
}
// HandleItemAttachmentGet godocs
// @Summary retrieves an attachment for an item
// @Tags Items Attachments
// @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() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
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")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
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)
return nil
}
}
// HandleItemAttachmentToken godocs
// @Summary retrieves an attachment for an item
// @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) HandleItemAttachmentToken() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentDelete godocs
// @Summary retrieves an attachment for an item
// @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() server.HandlerFunc {
return ctrl.handleItemAttachmentsHandler
}
// HandleItemAttachmentUpdate godocs
// @Summary retrieves an attachment for an item
// @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() server.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 {
// Token Handler
case http.MethodGet:
token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentID)
if err != nil {
switch err {
case services.ErrNotFound:
log.Err(err).
Str("id", attachmentID.String()).
Msg("failed to find attachment with id")
return validate.NewRequestError(err, http.StatusNotFound)
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")
return validate.NewRequestError(err, http.StatusNotFound)
default:
log.Err(err).Msg("failed to get attachment")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
}
return server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
// 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.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")
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.Respond(w, http.StatusOK, val)
}
return nil
}

View File

@@ -1,143 +0,0 @@
package v1
import (
"net/http"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// 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() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
user := services.UseUserCtx(r.Context())
labels, err := ctrl.repo.Labels.GetAll(r.Context(), user.GroupID)
if err != nil {
log.Err(err).Msg("error getting labels")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, server.Results{Items: labels})
}
}
// 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() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
createData := repo.LabelCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("error decoding label create data")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
user := services.UseUserCtx(r.Context())
label, err := ctrl.repo.Labels.Create(r.Context(), user.GroupID, createData)
if err != nil {
log.Err(err).Msg("error creating label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusCreated, label)
}
}
// HandleLabelDelete godocs
// @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() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
// 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() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
// 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() server.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
func (ctrl *V1Controller) handleLabelsGeneral() server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
ID, err := ctrl.routeID(r)
if err != nil {
return err
}
switch r.Method {
case http.MethodGet:
labels, err := ctrl.repo.Labels.GetOneByGroup(r.Context(), ctx.GID, ID)
if err != nil {
if ent.IsNotFound(err) {
log.Err(err).
Str("id", ID.String()).
Msg("label not found")
return validate.NewRequestError(err, http.StatusNotFound)
}
log.Err(err).Msg("error getting label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, labels)
case http.MethodDelete:
err = ctrl.repo.Labels.DeleteByGroup(ctx, ctx.GID, ID)
if err != nil {
log.Err(err).Msg("error deleting label")
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
body := repo.LabelUpdate{}
if err := server.Decode(r, &body); err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
body.ID = ID
result, err := ctrl.repo.Labels.UpdateByGroup(ctx, ctx.GID, body)
if err != nil {
return validate.NewRequestError(err, http.StatusInternalServerError)
}
return server.Respond(w, http.StatusOK, result)
}
return nil
}
}

View File

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

View File

@@ -1,148 +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/sys/validate"
"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() server.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(nil, 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.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() server.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.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() server.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.Respond(w, http.StatusOK, server.Wrap(newData))
}
}
// HandleUserSelfDelete godoc
// @Summary Deletes the user account
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfDelete() server.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.Respond(w, http.StatusNoContent, nil)
}
}
type (
ChangePassword struct {
Current string `json:"current,omitempty"`
New string `json:"new,omitempty"`
}
)
// HandleUserSelfChangePassword godoc
// @Summary Updates the users password
// @Tags User
// @Success 204
// @Param payload body ChangePassword true "Password Payload"
// @Router /v1/users/change-password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfChangePassword() server.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.Respond(w, http.StatusNoContent, nil)
}
}

View File

@@ -4,7 +4,7 @@ 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"
)
@@ -15,7 +15,7 @@ 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})
}
log.Level(getLevel(a.conf.Log.Level))

View File

@@ -2,41 +2,39 @@ package main
import (
"context"
"net/http"
"os"
"path/filepath"
"time"
atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql/schema"
"github.com/hay-kot/homebox/backend/app/api/static/docs"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/data/ent"
"github.com/hay-kot/homebox/backend/internal/data/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/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"
)
var (
version = "nightly"
commit = "HEAD"
buildTime = "now"
Version = "0.1.0"
Commit = "HEAD"
BuildTime = "now"
)
// @title Go API Templates
// @version 1.0
// @description This is a simple Rest API Server Template that implements some basic User and Authentication patterns to help you get started and bootstrap your next project!.
// @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"
// @title Go API Templates
// @version 1.0
// @description This is a simple Rest API Server Template that implements some basic User and Authentication patterns to help you get started and bootstrap your next project!.
// @contact.name Don't
// @license.name MIT
// @BasePath /api
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
func main() {
cfg, err := config.New()
if err != nil {
@@ -112,28 +110,20 @@ func run(cfg *config.Config) error {
app.db = c
app.repos = repo.New(c, cfg.Storage.Data)
app.services = services.New(app.repos)
app.services = services.NewServices(app.repos)
// =========================================================================
// Start Server\
logger := log.With().Caller().Logger()
mwLogger := mid.Logger(logger)
if app.conf.Mode == config.ModeDevelopment {
mwLogger = mid.SugarLogger(logger)
}
// Start Server
app.server = server.NewServer(
server.WithHost(app.conf.Web.Host),
server.WithPort(app.conf.Web.Port),
server.WithMiddleware(
mwLogger,
mid.Errors(logger),
mid.Panic(app.conf.Mode == config.ModeDevelopment),
),
)
app.mountRoutes(app.repos)
routes := app.newRouter(app.repos)
if app.conf.Mode != config.ModeDevelopment {
app.logRoutes(routes)
}
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
@@ -148,14 +138,6 @@ func run(cfg *config.Config) error {
Msg("failed to purge expired tokens")
}
})
go app.startBgTask(time.Duration(24)*time.Hour, func() {
_, err := app.repos.Groups.InvitationPurge(context.Background())
if err != nil {
log.Error().
Err(err).
Msg("failed to purge expired invitations")
}
})
// TODO: Remove through external API that does setup
if cfg.Demo {
@@ -163,14 +145,5 @@ func run(cfg *config.Config) error {
app.SetupDemo()
}
if cfg.Debug.Enabled {
debugrouter := app.debugRouter()
go func() {
if err := http.ListenAndServe(":"+cfg.Debug.Port, debugrouter); err != nil {
log.Fatal().Err(err).Msg("failed to start debug server")
}
}()
}
return app.server.Start()
return app.server.Start(routes)
}

View File

@@ -1,34 +1,160 @@
package main
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/hay-kot/homebox/backend/internal/core/services"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"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"
)
func (a *app) setGlobalMiddleware(r *chi.Mux) {
// =========================================================================
// Middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(mwStripTrailingSlash)
// Use struct logger in production for requests, but use
// pretty console logger in development.
if a.conf.Mode == config.ModeDevelopment {
r.Use(a.mwSummaryLogger)
} else {
r.Use(a.mwStructLogger)
}
r.Use(middleware.Recoverer)
// Set a timeout value on the request context (ctx), that will signal
// through ctx.Done() that the request has timed out and further
// processing should be stopped.
r.Use(middleware.Timeout(60 * time.Second))
}
// mwAuthToken is a middleware that will check the database for a stateful token
// and attach it to the request context with the user, or return a 401 if it doesn't exist.
func (a *app) mwAuthToken(next server.Handler) server.Handler {
return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
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 == "" {
return validate.NewRequestError(errors.New("Authorization header is required"), http.StatusUnauthorized)
server.RespondUnauthorized(w)
return
}
requestToken = strings.TrimPrefix(requestToken, "Bearer ")
usr, err := a.services.User.GetSelf(r.Context(), requestToken)
// Check the database for the token
if err != nil {
return validate.NewRequestError(errors.New("Authorization header is required"), http.StatusUnauthorized)
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

@@ -10,11 +10,11 @@ import (
"path"
"path/filepath"
"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/static/docs"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/go-chi/chi/v5"
_ "github.com/hay-kot/homebox/backend/app/api/docs"
v1 "github.com/hay-kot/homebox/backend/app/api/v1"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/rs/zerolog/log"
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
)
@@ -23,90 +23,102 @@ 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(repos *repo.AllRepos) {
func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
registerMimes()
a.server.Get("/swagger/*", server.ToHandler(httpSwagger.Handler(
r := chi.NewRouter()
a.setGlobalMiddleware(r)
r.Get("/swagger/*", httpSwagger.Handler(
httpSwagger.URL(fmt.Sprintf("%s://%s/swagger/doc.json", a.conf.Swagger.Scheme, a.conf.Swagger.Host)),
)))
))
// =========================================================================
// API Version 1
v1Base := v1.BaseUrlFunc(prefix)
v1Ctrl := v1.NewControllerV1(
a.services,
a.repos,
v1Ctrl := v1.NewControllerV1(a.services,
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
v1.WithRegistration(a.conf.AllowRegistration),
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
)
a.server.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: version,
Commit: commit,
BuildTime: buildTime,
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: Version,
Commit: Commit,
BuildTime: BuildTime,
}))
a.server.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
a.server.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
r.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration())
r.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin())
// Attachment download URl needs a `token` query param to be passed in the request.
// and also needs to be outside of the `auth` middleware.
a.server.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload())
r.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload())
a.server.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf(), a.mwAuthToken)
a.server.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate(), a.mwAuthToken)
a.server.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete(), a.mwAuthToken)
a.server.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout(), a.mwAuthToken)
a.server.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh(), a.mwAuthToken)
a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), a.mwAuthToken)
r.Group(func(r chi.Router) {
r.Use(a.mwAuthToken)
r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf())
r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate())
r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete())
r.Put(v1Base("/users/self/password"), v1Ctrl.HandleUserUpdatePassword())
r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout())
r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh())
r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword())
a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), a.mwAuthToken)
a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), a.mwAuthToken)
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
// TODO: I don't like /groups being the URL for users
a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), a.mwAuthToken)
a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), a.mwAuthToken)
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())
a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), a.mwAuthToken)
a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), a.mwAuthToken)
a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), a.mwAuthToken)
a.server.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate(), a.mwAuthToken)
a.server.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete(), a.mwAuthToken)
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())
a.server.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll(), a.mwAuthToken)
a.server.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate(), a.mwAuthToken)
a.server.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet(), a.mwAuthToken)
a.server.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate(), a.mwAuthToken)
a.server.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete(), a.mwAuthToken)
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())
a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), a.mwAuthToken)
a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), a.mwAuthToken)
a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), a.mwAuthToken)
a.server.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet(), a.mwAuthToken)
a.server.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate(), a.mwAuthToken)
a.server.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete(), a.mwAuthToken)
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())
})
a.server.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate(), a.mwAuthToken)
a.server.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken(), a.mwAuthToken)
a.server.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate(), a.mwAuthToken)
a.server.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete(), a.mwAuthToken)
r.NotFound(notFoundHandler())
return r
}
a.server.NotFound(notFoundHandler())
// logRoutes logs the routes of the server that are registered within Server.registerRoutes(). This is useful for debugging.
// See https://github.com/go-chi/chi/issues/332 for details and inspiration.
func (a *app) logRoutes(r *chi.Mux) {
desiredSpaces := 10
walkFunc := func(method string, route string, handler http.Handler, middleware ...func(http.Handler) http.Handler) error {
text := "[" + method + "]"
for len(text) < desiredSpaces {
text = text + " "
}
fmt.Printf("Registered Route: %s%s\n", text, route)
return nil
}
if err := chi.Walk(r, walkFunc); err != nil {
fmt.Printf("Logging err: %s\n", err.Error())
}
}
func registerMimes() {
@@ -123,7 +135,7 @@ 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() server.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 {
@@ -142,16 +154,17 @@ func notFoundHandler() server.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
}
}

View File

@@ -3,8 +3,7 @@ package v1
import (
"net/http"
"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/services"
"github.com/hay-kot/homebox/backend/pkgs/server"
)
@@ -20,23 +19,13 @@ func WithDemoStatus(demoStatus bool) func(*V1Controller) {
}
}
func WithRegistration(allowRegistration bool) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.allowRegistration = allowRegistration
}
}
type V1Controller struct {
repo *repo.AllRepos
svc *services.AllServices
maxUploadSize int64
isDemo bool
allowRegistration bool
svc *services.AllServices
maxUploadSize int64
isDemo bool
}
type (
ReadyFunc func() bool
Build struct {
Version string `json:"version"`
Commit string `json:"commit"`
@@ -54,16 +43,17 @@ type (
)
func BaseUrlFunc(prefix string) func(s string) string {
return func(s string) string {
return prefix + "/v1" + s
v1Base := prefix + "/v1"
prefixFunc := func(s string) string {
return v1Base + s
}
return prefixFunc
}
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, options ...func(*V1Controller)) *V1Controller {
func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller {
ctrl := &V1Controller{
repo: repos,
svc: svc,
allowRegistration: true,
svc: svc,
}
for _, opt := range options {
@@ -73,15 +63,17 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, options ..
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) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
return server.Respond(w, http.StatusOK, ApiSummary{
// @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!",

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

View File

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

View File

@@ -5,7 +5,7 @@ import (
"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"
@@ -17,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)
}

View File

@@ -9,9 +9,9 @@ import (
"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.

View File

@@ -95,7 +95,6 @@ const (
TypeManual Type = "manual"
TypeWarranty Type = "warranty"
TypeAttachment Type = "attachment"
TypeReceipt Type = "receipt"
)
func (_type Type) String() string {
@@ -105,7 +104,7 @@ 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)

View File

@@ -8,7 +8,7 @@ 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.

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.

View File

@@ -9,8 +9,8 @@ import (
"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.

View File

@@ -11,10 +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/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.

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.

View File

@@ -9,8 +9,8 @@ import (
"entgo.io/ent/dialect/sql"
"github.com/google/uuid"
"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.

View File

@@ -8,7 +8,7 @@ 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.

View File

@@ -11,8 +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/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.

View File

@@ -9,8 +9,8 @@ import (
"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.

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/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.

View File

@@ -12,9 +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/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.

View File

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

View File

@@ -9,8 +9,8 @@ import (
"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.

View File

@@ -8,7 +8,7 @@ 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.

View File

@@ -11,10 +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/documenttoken"
"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.

View File

@@ -9,8 +9,8 @@ import (
"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.

View File

@@ -12,11 +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/documenttoken"
"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.

View File

@@ -12,11 +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/documenttoken"
"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.

View File

@@ -9,8 +9,8 @@ import (
"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/documenttoken"
"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.

View File

@@ -8,7 +8,7 @@ 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.

View File

@@ -11,8 +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/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
)
// DocumentTokenCreate is the builder for creating a DocumentToken entity.

View File

@@ -9,8 +9,8 @@ import (
"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/documenttoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentTokenDelete is the builder for deleting a DocumentToken entity.

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/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"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/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentTokenQuery is the builder for querying DocumentToken entities.

View File

@@ -12,9 +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/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken"
"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/documenttoken"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// DocumentTokenUpdate is the builder for updating DocumentToken entities.

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/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.

View File

@@ -11,13 +11,13 @@ 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/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/user"
"github.com/hay-kot/homebox/backend/ent/document"
"github.com/hay-kot/homebox/backend/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/item"
"github.com/hay-kot/homebox/backend/ent/label"
"github.com/hay-kot/homebox/backend/ent/location"
"github.com/hay-kot/homebox/backend/ent/user"
)
// GroupCreate is the builder for creating a Group entity.

View File

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

View File

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

View File

@@ -12,14 +12,14 @@ 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/document"
"github.com/hay-kot/homebox/backend/internal/data/ent/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"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/document"
"github.com/hay-kot/homebox/backend/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/item"
"github.com/hay-kot/homebox/backend/ent/label"
"github.com/hay-kot/homebox/backend/ent/location"
"github.com/hay-kot/homebox/backend/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/user"
)
// GroupUpdate is the builder for updating Group entities.

View File

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

View File

@@ -8,7 +8,7 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/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.

View File

@@ -11,8 +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/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken"
)
// GroupInvitationTokenCreate is the builder for creating a GroupInvitationToken entity.

View File

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

View File

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

View File

@@ -12,9 +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/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/group"
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// GroupInvitationTokenUpdate is the builder for updating GroupInvitationToken entities.

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ 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.
@@ -138,13 +138,6 @@ func Insured(v bool) predicate.Item {
})
}
// Archived applies equality check predicate on the "archived" field. It's identical to ArchivedEQ.
func Archived(v bool) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldArchived), v))
})
}
// SerialNumber applies equality check predicate on the "serial_number" field. It's identical to SerialNumberEQ.
func SerialNumber(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
@@ -880,20 +873,6 @@ func InsuredNEQ(v bool) predicate.Item {
})
}
// ArchivedEQ applies the EQ predicate on the "archived" field.
func ArchivedEQ(v bool) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldArchived), v))
})
}
// ArchivedNEQ applies the NEQ predicate on the "archived" field.
func ArchivedNEQ(v bool) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldArchived), v))
})
}
// SerialNumberEQ applies the EQ predicate on the "serial_number" field.
func SerialNumberEQ(v string) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
@@ -2061,62 +2040,6 @@ func SoldNotesContainsFold(v string) predicate.Item {
})
}
// HasParent applies the HasEdge predicate on the "parent" edge.
func HasParent() predicate.Item {
return predicate.Item(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ParentTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasParentWith applies the HasEdge predicate on the "parent" edge with a given conditions (other predicates).
func HasParentWith(preds ...predicate.Item) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasChildren applies the HasEdge predicate on the "children" edge.
func HasChildren() predicate.Item {
return predicate.Item(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ChildrenTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasChildrenWith applies the HasEdge predicate on the "children" edge with a given conditions (other predicates).
func HasChildrenWith(preds ...predicate.Item) predicate.Item {
return predicate.Item(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn),
)
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasGroup applies the HasEdge predicate on the "group" edge.
func HasGroup() predicate.Item {
return predicate.Item(func(s *sql.Selector) {

View File

@@ -11,12 +11,12 @@ 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/group"
"github.com/hay-kot/homebox/backend/internal/data/ent/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/itemfield"
"github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/ent/group"
"github.com/hay-kot/homebox/backend/ent/item"
"github.com/hay-kot/homebox/backend/ent/itemfield"
"github.com/hay-kot/homebox/backend/ent/label"
"github.com/hay-kot/homebox/backend/ent/location"
)
// ItemCreate is the builder for creating a Item entity.
@@ -130,20 +130,6 @@ func (ic *ItemCreate) SetNillableInsured(b *bool) *ItemCreate {
return ic
}
// SetArchived sets the "archived" field.
func (ic *ItemCreate) SetArchived(b bool) *ItemCreate {
ic.mutation.SetArchived(b)
return ic
}
// SetNillableArchived sets the "archived" field if the given value is not nil.
func (ic *ItemCreate) SetNillableArchived(b *bool) *ItemCreate {
if b != nil {
ic.SetArchived(*b)
}
return ic
}
// SetSerialNumber sets the "serial_number" field.
func (ic *ItemCreate) SetSerialNumber(s string) *ItemCreate {
ic.mutation.SetSerialNumber(s)
@@ -340,40 +326,6 @@ func (ic *ItemCreate) SetNillableID(u *uuid.UUID) *ItemCreate {
return ic
}
// SetParentID sets the "parent" edge to the Item entity by ID.
func (ic *ItemCreate) SetParentID(id uuid.UUID) *ItemCreate {
ic.mutation.SetParentID(id)
return ic
}
// SetNillableParentID sets the "parent" edge to the Item entity by ID if the given value is not nil.
func (ic *ItemCreate) SetNillableParentID(id *uuid.UUID) *ItemCreate {
if id != nil {
ic = ic.SetParentID(*id)
}
return ic
}
// SetParent sets the "parent" edge to the Item entity.
func (ic *ItemCreate) SetParent(i *Item) *ItemCreate {
return ic.SetParentID(i.ID)
}
// AddChildIDs adds the "children" edge to the Item entity by IDs.
func (ic *ItemCreate) AddChildIDs(ids ...uuid.UUID) *ItemCreate {
ic.mutation.AddChildIDs(ids...)
return ic
}
// AddChildren adds the "children" edges to the Item entity.
func (ic *ItemCreate) AddChildren(i ...*Item) *ItemCreate {
ids := make([]uuid.UUID, len(i))
for j := range i {
ids[j] = i[j].ID
}
return ic.AddChildIDs(ids...)
}
// SetGroupID sets the "group" edge to the Group entity by ID.
func (ic *ItemCreate) SetGroupID(id uuid.UUID) *ItemCreate {
ic.mutation.SetGroupID(id)
@@ -542,10 +494,6 @@ func (ic *ItemCreate) defaults() {
v := item.DefaultInsured
ic.mutation.SetInsured(v)
}
if _, ok := ic.mutation.Archived(); !ok {
v := item.DefaultArchived
ic.mutation.SetArchived(v)
}
if _, ok := ic.mutation.LifetimeWarranty(); !ok {
v := item.DefaultLifetimeWarranty
ic.mutation.SetLifetimeWarranty(v)
@@ -601,9 +549,6 @@ func (ic *ItemCreate) check() error {
if _, ok := ic.mutation.Insured(); !ok {
return &ValidationError{Name: "insured", err: errors.New(`ent: missing required field "Item.insured"`)}
}
if _, ok := ic.mutation.Archived(); !ok {
return &ValidationError{Name: "archived", err: errors.New(`ent: missing required field "Item.archived"`)}
}
if v, ok := ic.mutation.SerialNumber(); ok {
if err := item.SerialNumberValidator(v); err != nil {
return &ValidationError{Name: "serial_number", err: fmt.Errorf(`ent: validator failed for field "Item.serial_number": %w`, err)}
@@ -741,14 +686,6 @@ func (ic *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) {
})
_node.Insured = value
}
if value, ok := ic.mutation.Archived(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeBool,
Value: value,
Column: item.FieldArchived,
})
_node.Archived = value
}
if value, ok := ic.mutation.SerialNumber(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeString,
@@ -853,45 +790,6 @@ func (ic *ItemCreate) createSpec() (*Item, *sqlgraph.CreateSpec) {
})
_node.SoldNotes = value
}
if nodes := ic.mutation.ParentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: item.ParentTable,
Columns: []string{item.ParentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.item_children = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := ic.mutation.ChildrenIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: item.ChildrenTable,
Columns: []string{item.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: &sqlgraph.FieldSpec{
Type: field.TypeUUID,
Column: item.FieldID,
},
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := ic.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,

View File

@@ -9,8 +9,8 @@ import (
"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/item"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/ent/item"
"github.com/hay-kot/homebox/backend/ent/predicate"
)
// ItemDelete is the builder for deleting a Item entity.

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